summaryrefslogtreecommitdiffstats
path: root/src/Auth/Confirmation.php
blob: 3a4e6311097884454492f6d7d3c88cbd7896314f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
<?php

namespace Jasny\Auth;

use Hashids\Hashids;

/**
 * Generate and verify confirmation tokens.
 * 
 * Uses the hashids library.
 * @link http://hashids.org/php/
 * 
 * <code>
 * class Auth extends Jasny\Auth
 * {
 *   use Jasny\Auth\Confirmation;
 * 
 *   public function getConfirmationSecret()
 *   {
 *     return "f)lk3sd^92qlj$%f8321*(&lk";
 *   }
 * 
 *   ...
 * }
 * </code>
 */
trait Confirmation
{
    /**
     * Fetch a user by ID
     * 
     * @param int|string $id
     * @return User|null
     */
    abstract public function fetchUserById($id);
    
    /**
     * Get secret for the confirmation hash
     * 
     * @return string
     */
    abstract protected function getConfirmationSecret();

    
    /**
     * Create a heashids interface
     * 
     * @param string $subject
     * @return Hashids
     */
    protected function createHashids($subject)
    {
        if (!class_exists(Hashids::class)) {
            // @codeCoverageIgnoreStart
            throw new \Exception("Unable to generate a confirmation hash: Hashids library is not installed");
            // @codeCoverageIgnoreEnd
        }
        
        $salt = hash('sha256', $this->getConfirmationSecret() . $subject);
        
        return new Hashids($salt);
    }
    
    /**
     * Generate a confirm checksum based on a user id and secret.
     * 
     * For more entropy overwrite this method:
     * <code>
     *   protected function getConfirmationChecksum($id, $len = 32)
     *   {
     *     return parent::getConfirmationChecksum($id, $len);
     *   }
     * </code>
     * 
     * @param string $id
     * @param int    $len  The number of characters of the hash (max 64)
     * @return int
     */
    protected function getConfirmationChecksum($id, $len = 16)
    {
        $hash = hash('sha256', $id . $this->getConfirmationSecret());
        return substr($hash, 0, $len);
    }
    
    /**
     * Generate a confirmation token
     * 
     * @param User    $user
     * @param string  $subject      What needs to be confirmed?
     * @param boolean $usePassword  Use password hash in checksum
     * @return string
     */
    public function getConfirmationToken(User $user, $subject, $usePassword = false)
    {
        $hashids = $this->createHashids($subject);
        
        $id = $user->getId();
        $pwd = $usePassword ? $user->getHashedPassword() : '';
        
        $confirm = $this->getConfirmationChecksum($id . $pwd);
        
        return $hashids->encodeHex($confirm . $id);
    }
    
    /**
     * Get user by confirmation hash
     * 
     * @param string $token    Confirmation token
     * @param string $subject  What needs to be confirmed?
     * @param boolean $usePassword  Use password hash in checksum
     * @return User|null
     */
    public function fetchUserForConfirmation($token, $subject, $usePassword = false)
    {
        $hashids = $this->createHashids($subject);
        
        $idAndConfirm = $hashids->decodeHex($token);
        
        if (empty($idAndConfirm)) {
            return null;
        }
        
        $len = strlen($this->getConfirmationChecksum(''));
        $id = substr($idAndConfirm, $len);
        $confirm = substr($idAndConfirm, 0, $len);
        
        $user = $this->fetchUserById($id);

        if (!isset($user)) {
            return null;
        }
        
        $pwd = $usePassword ? $user->getHashedPassword() : '';
        
        if ($confirm !== $this->getConfirmationChecksum($id . $pwd)) {
            return null;
        }
        
        return $user;
    }
}