1
1
import { hmac } from "./hmac" ;
2
2
import type { SHAFamily } from "./type" ;
3
3
4
- export const createOTP = (
5
- hash : SHAFamily = "SHA-1" ,
6
- digits = 6 ,
7
- seconds = 30 ,
8
- ) => {
9
- const defaultSeconds = seconds ;
10
- const defaultDigits = digits ;
11
- async function generateHOTP (
12
- secret : string ,
13
- {
14
- counter,
15
- digits,
16
- } : {
17
- counter : number ;
18
- digits ?: number ;
19
- } ,
20
- ) {
21
- const _digits = digits ?? defaultDigits ;
22
- if ( _digits < 1 || _digits > 8 ) {
23
- throw new TypeError ( "Digits must be between 1 and 8" ) ;
24
- }
25
- const buffer = new ArrayBuffer ( 8 ) ;
26
- new DataView ( buffer ) . setBigUint64 ( 0 , BigInt ( counter ) , false ) ;
27
- const bytes = new Uint8Array ( buffer ) ;
28
- const hmacResult = new Uint8Array ( await hmac . sign ( secret , {
29
- data : bytes ,
30
- hash
31
- } ) ) ;
32
- const offset = hmacResult [ hmacResult . length - 1 ] & 0x0f ;
33
- const truncated =
34
- ( ( hmacResult [ offset ] & 0x7f ) << 24 ) |
35
- ( ( hmacResult [ offset + 1 ] & 0xff ) << 16 ) |
36
- ( ( hmacResult [ offset + 2 ] & 0xff ) << 8 ) |
37
- ( hmacResult [ offset + 3 ] & 0xff ) ;
38
- const otp = truncated % 10 ** _digits ;
39
- return otp . toString ( ) . padStart ( _digits , "0" ) ;
40
- }
41
- async function generateTOTP (
42
- secret : string ,
43
- {
44
- seconds = defaultSeconds ,
45
- digits = defaultDigits ,
46
- } : {
47
- seconds ?: number ;
48
- digits ?: number ;
49
- } ,
50
- ) {
51
- const milliseconds = seconds * 1000 ;
52
- const counter = Math . floor ( Date . now ( ) / milliseconds ) ;
53
- return await generateHOTP ( secret , { counter, digits } ) ;
4
+ const defaultPeriod = 30 ;
5
+ const defaultDigits = 6 ;
6
+
7
+ export async function generateHOTP (
8
+ secret : string ,
9
+ {
10
+ counter,
11
+ digits,
12
+ hash = "SHA-1" ,
13
+ } : {
14
+ counter : number ;
15
+ digits ?: number ;
16
+ hash ?: SHAFamily ;
17
+ } ,
18
+ ) {
19
+ const _digits = digits ?? defaultDigits ;
20
+ if ( _digits < 1 || _digits > 8 ) {
21
+ throw new TypeError ( "Digits must be between 1 and 8" ) ;
54
22
}
23
+ const buffer = new ArrayBuffer ( 8 ) ;
24
+ new DataView ( buffer ) . setBigUint64 ( 0 , BigInt ( counter ) , false ) ;
25
+ const bytes = new Uint8Array ( buffer ) ;
26
+ const hmacResult = new Uint8Array ( await hmac . sign ( secret , {
27
+ data : bytes ,
28
+ hash
29
+ } ) ) ;
30
+ const offset = hmacResult [ hmacResult . length - 1 ] & 0x0f ;
31
+ const truncated =
32
+ ( ( hmacResult [ offset ] & 0x7f ) << 24 ) |
33
+ ( ( hmacResult [ offset + 1 ] & 0xff ) << 16 ) |
34
+ ( ( hmacResult [ offset + 2 ] & 0xff ) << 8 ) |
35
+ ( hmacResult [ offset + 3 ] & 0xff ) ;
36
+ const otp = truncated % 10 ** _digits ;
37
+ return otp . toString ( ) . padStart ( _digits , "0" ) ;
38
+ }
39
+
40
+ export async function generateTOTP (
41
+ secret : string ,
42
+ {
43
+ period = defaultPeriod ,
44
+ digits = defaultDigits ,
45
+ } : {
46
+ period ?: number ;
47
+ digits ?: number ;
48
+ } ,
49
+ ) {
50
+ const milliseconds = period * 1000 ;
51
+ const counter = Math . floor ( Date . now ( ) / milliseconds ) ;
52
+ return await generateHOTP ( secret , { counter, digits } ) ;
53
+ }
55
54
56
- async function verifyTOTP (
57
- otp : string ,
58
- {
59
- window = 1 ,
60
- digits = defaultDigits ,
61
- secret ,
62
- seconds = defaultSeconds ,
63
- } : {
64
- seconds ?: number ;
65
- window ?: number ;
66
- digits ?: number ;
67
- secret : string ;
68
- } ,
69
- ) {
70
- const milliseconds = seconds * 1000 ;
71
- const counter = Math . floor ( Date . now ( ) / milliseconds ) ;
72
- for ( let i = - window ; i <= window ; i ++ ) {
73
- const generatedOTP = await generateHOTP ( secret , {
74
- counter : counter + i ,
75
- digits ,
76
- } ) ;
77
- if ( otp === generatedOTP ) {
78
- return true ;
79
- }
55
+
56
+ export async function verifyTOTP (
57
+ otp : string ,
58
+ {
59
+ window = 1 ,
60
+ digits = defaultDigits ,
61
+ secret ,
62
+ period = defaultPeriod ,
63
+ } : {
64
+ period ?: number ;
65
+ window ?: number ;
66
+ digits ?: number ;
67
+ secret : string ;
68
+ } ,
69
+ ) {
70
+ const milliseconds = period * 1000 ;
71
+ const counter = Math . floor ( Date . now ( ) / milliseconds ) ;
72
+ for ( let i = - window ; i <= window ; i ++ ) {
73
+ const generatedOTP = await generateHOTP ( secret , {
74
+ counter : counter + i ,
75
+ digits ,
76
+ } ) ;
77
+ if ( otp === generatedOTP ) {
78
+ return true ;
80
79
}
81
- return false ;
82
80
}
81
+ return false ;
82
+ }
83
83
84
- /**
84
+ /**
85
85
* Generate a QR code URL for the OTP secret
86
86
*/
87
- function generateQRCode (
87
+ export function generateQRCode (
88
+ {
89
+ issuer,
90
+ account,
91
+ secret,
92
+ digit = defaultDigits ,
93
+ period = defaultPeriod ,
94
+ } : {
88
95
issuer : string ,
89
96
account : string ,
90
97
secret : string ,
91
- ) {
92
- const url = new URL ( "otpauth://totp" ) ;
93
- url . searchParams . set ( "secret" , secret ) ;
94
- url . searchParams . set ( "issuer" , issuer ) ;
95
- url . searchParams . set ( "account" , account ) ;
96
- return url . toString ( ) ;
98
+ digit ?: number ,
99
+ period ?: number ,
97
100
}
98
- return {
99
- generateHOTP,
100
- generateTOTP,
101
- verifyTOTP,
102
- generateQRCode
103
- } ;
104
- } ;
101
+ ) {
102
+ const url = new URL ( "otpauth://totp" ) ;
103
+ url . searchParams . set ( "secret" , secret ) ;
104
+ url . searchParams . set ( "issuer" , issuer ) ;
105
+ url . searchParams . set ( "account" , account ) ;
106
+ url . searchParams . set ( "digits" , digit . toString ( ) ) ;
107
+ url . searchParams . set ( "period" , period . toString ( ) ) ;
108
+ return url . toString ( ) ;
109
+ }
0 commit comments