Passed
Push — master ( 051f8c...0effca )
by Daniel
02:41
created

OtpGenerator   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 207
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 86
dl 0
loc 207
rs 10
c 0
b 0
f 0
wmc 19

7 Methods

Rating   Name   Duplication   Size   Complexity  
A createPin() 0 14 3
A __construct() 0 9 1
A generate() 0 37 4
A deleteOldOtps() 0 5 1
A validate() 0 37 5
A expiredAt() 0 14 2
A __call() 0 17 3
1
<?php
2
3
namespace DanielRobert\Otp;
4
5
use Carbon\Carbon;
6
use DanielRobert\Otp\Models\OtpModel;
7
use Illuminate\Support\Str;
8
9
10
class OtpGenerator {
11
    /**
12
     * Length of the generated Otp
13
     *
14
     * @var int
15
     */
16
    protected $length;
17
18
    /**
19
     * Generated OPT type
20
     *
21
     * @var bool
22
     */
23
    protected $onlyDigits;
24
25
    /**
26
     * use same token to resending opt
27
     *
28
     *  @var bool
29
     */
30
    protected $useSameToken;
31
32
    /**
33
     * Otp Validity time
34
     *
35
     * @var int
36
     */
37
    protected $validity;
38
39
    /**
40
     * Delete old otps
41
     *
42
     * @var int
43
     */
44
    protected $deleteOldOtps;
45
46
    /**
47
     * Maximum otps allowed to generate
48
     *
49
     *  @var int
50
     */
51
    protected $maximumOtpsAllowed;
52
53
    /**
54
     * Maximum number of times to allowed to validate
55
     *
56
     * @var int
57
     */
58
    protected $allowedAttempts;
59
60
    public function __construct()
61
    {
62
        $this->length = config('otp-generator.length');
63
        $this->onlyDigits = config('otp-generator.onlyDigits');
64
        $this->useSameToken = config('otp-generator.useSameToken');
65
        $this->validity = config('otp-generator.validity');
66
        $this->deleteOldOtps = config('otp-generator.deleteOldOtps');
67
        $this->maximumOtpsAllowed = config('otp-generator.maximumOtpsAllowed');
68
        $this->allowedAttempts = config('otp-generator.allowedAttempts');
69
    }
70
71
    /**
72
     * When a method is called, look for the 'set' prefix and attempt to set the
73
     * matching property to the value passed to the method and return a chainable
74
     * object to the caller.
75
     *
76
     * @param string $method
77
     * @param mixed $params
78
     * @return mixed
79
     */
80
    public function __call(string $method, $params)
81
    {
82
        if (substr($method, 0, 3) != 'set') {
83
            return;
84
        }
85
86
        $property = Str::camel(substr($method, 3));
87
88
89
        // Does the property exist on this object?
90
        if (! property_exists($this, $property)) {
91
            return;
92
        }
93
94
        $this->{$property} = $params[0] ?? null;
95
96
        return $this;
97
    }
98
99
    public function generate(string $identifier): object
100
    {
101
        $this->deleteOldOtps();
102
103
        $otp = OtpModel::where('identifier', $identifier)->first();
104
105
        if ($otp == null) {
106
            $otp = OtpModel::create([
107
                'identifier' => $identifier,
108
                'token' => $this->createPin(),
109
                'validity' => $this->validity,
110
                'generated_at' => Carbon::now(),
111
            ]);
112
        } else {
113
            if ($otp->no_times_generated == $this->maximumOtpsAllowed) {
0 ignored issues
show
Bug introduced by
The property no_times_generated does not seem to exist on Illuminate\Database\Eloq...Relations\HasOneThrough.
Loading history...
Bug introduced by
The property no_times_generated does not seem to exist on Illuminate\Database\Eloq...elations\HasManyThrough.
Loading history...
114
                return (object) [
115
                    'status' => false,
116
                    'message' => "Reached the maximum times to generate Otp",
117
                ];
118
            }
119
120
            $otp->update([
121
                'identifier' => $identifier,
122
                'token' => $this->useSameToken ? $otp->token : $this->createPin(),
0 ignored issues
show
Bug introduced by
The property token does not seem to exist on Illuminate\Database\Eloq...Relations\HasOneThrough.
Loading history...
Bug introduced by
The property token does not seem to exist on Illuminate\Database\Eloq...elations\HasManyThrough.
Loading history...
123
                'validity' => $this->validity,
124
                'generated_at' => Carbon::now(),
125
            ]);
126
        }
127
128
129
        $otp->increment('no_times_generated');
130
131
132
        return (object) [
133
            'status' => true,
134
            'token' => $otp->token,
135
            'message' => "Otp generated",
136
        ];
137
    }
138
139
    public function validate(string $identifier, string $token): object
140
    {
141
        $otp = OtpModel::where('identifier', $identifier)->first();
142
143
        if (! $otp) {
144
            return (object) [
145
                'status' => false,
146
                'message' => 'Otp does not exists, Please generate new Otp',
147
            ];
148
        }
149
150
        if ($otp->isExpired()) {
151
            return (object) [
152
                'status' => false,
153
                'message' => 'Otp is expired',
154
            ];
155
        }
156
157
        if ($otp->no_times_attempted == $this->allowedAttempts) {
0 ignored issues
show
Bug introduced by
The property no_times_attempted does not seem to exist on Illuminate\Database\Eloq...Relations\HasOneThrough.
Loading history...
Bug introduced by
The property no_times_attempted does not seem to exist on Illuminate\Database\Eloq...elations\HasManyThrough.
Loading history...
158
            return (object) [
159
                'status' => false,
160
                'message' => "Reached the maximum allowed attempts",
161
            ];
162
        }
163
164
        $otp->increment('no_times_attempted');
165
166
        if ($otp->token == $token) {
0 ignored issues
show
Bug introduced by
The property token does not seem to exist on Illuminate\Database\Eloq...Relations\HasOneThrough.
Loading history...
Bug introduced by
The property token does not seem to exist on Illuminate\Database\Eloq...elations\HasManyThrough.
Loading history...
167
            return (object) [
168
                'status' => true,
169
                'message' => 'Otp is valid',
170
            ];
171
        }
172
173
        return (object) [
174
            'status' => false,
175
            'message' => 'Otp does not match',
176
        ];
177
    }
178
179
    public function expiredAt(string $identifier): object
180
    {
181
        $otp = OtpModel::where('identifier', $identifier)->first();
182
183
        if (! $otp) {
184
            return (object) [
185
                'status' => false,
186
                'message' => 'Otp does not exists, Please generate new Otp',
187
            ];
188
        }
189
190
        return (object) [
191
            'status' => true,
192
            'expired_at' => $otp->expiredAt(),
193
        ];
194
    }
195
196
    private function deleteOldOtps()
197
    {
198
        OtpModel::where('expired', true)
199
            ->orWhere('created_at', '<', Carbon::now()->subMinutes($this->deleteOldOtps))
200
            ->delete();
201
    }
202
203
    private function createPin(): string
204
    {
205
        if ($this->onlyDigits) {
206
            $characters = '0123456789';
207
        } else {
208
            $characters = '123456789abcdefghABCDEFGH';
209
        }
210
        $length = strlen($characters);
211
        $pin = '';
212
        for ($i = 0; $i < $this->length; $i++) {
213
            $pin .= $characters[rand(0, $length - 1)];
214
        }
215
216
        return $pin;
217
    }
218
}