@1auth/authn 0.0.0-alpha.32 → 0.0.0-alpha.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +192 -94
- package/package.json +5 -5
package/index.js
CHANGED
|
@@ -1,74 +1,151 @@
|
|
|
1
1
|
import { setTimeout } from 'node:timers/promises'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
randomId,
|
|
4
|
+
makeSymetricKey,
|
|
5
|
+
symetricEncryptFields,
|
|
6
|
+
symetricDecryptFields
|
|
7
|
+
} from '@1auth/crypto'
|
|
3
8
|
|
|
4
|
-
|
|
9
|
+
const id = 'authn'
|
|
10
|
+
|
|
11
|
+
const defaults = {
|
|
12
|
+
id,
|
|
5
13
|
store: undefined,
|
|
6
14
|
notify: undefined,
|
|
7
15
|
table: 'authentications',
|
|
8
16
|
idGenerate: true,
|
|
9
17
|
idPrefix: 'authn',
|
|
10
|
-
randomId:
|
|
11
|
-
authenticationDuration: 500, //
|
|
12
|
-
usernameExists: [] // hooks to allow what to be used as a username
|
|
18
|
+
randomId: { ...randomId },
|
|
19
|
+
authenticationDuration: 500, // minimum duration authentication should take (ms)
|
|
20
|
+
usernameExists: [], // hooks to allow what to be used as a username
|
|
21
|
+
encryptedFields: ['value']
|
|
13
22
|
}
|
|
14
|
-
|
|
15
|
-
|
|
23
|
+
const options = {}
|
|
24
|
+
export default (opt = {}) => {
|
|
25
|
+
Object.assign(options, defaults, opt)
|
|
16
26
|
}
|
|
17
27
|
export const getOptions = () => options
|
|
18
28
|
|
|
29
|
+
export const exists = async (credentialOptions, sub, params) => {
|
|
30
|
+
const type = makeType(credentialOptions)
|
|
31
|
+
const list = await options.store.selectList(options.table, {
|
|
32
|
+
...params,
|
|
33
|
+
sub,
|
|
34
|
+
type
|
|
35
|
+
})
|
|
36
|
+
return list.length > 1
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const count = async (credentialOptions, sub) => {
|
|
40
|
+
const type = makeType(credentialOptions)
|
|
41
|
+
const credentials = await options.store.selectList(
|
|
42
|
+
options.table,
|
|
43
|
+
{ sub, type },
|
|
44
|
+
['verify', 'expire']
|
|
45
|
+
)
|
|
46
|
+
let count = 0
|
|
47
|
+
const now = nowInSeconds()
|
|
48
|
+
for (let i = credentials.length; i--;) {
|
|
49
|
+
const credential = credentials[i]
|
|
50
|
+
if (credential.expire && credential.expire < now) {
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
if (!credential.verify) {
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
56
|
+
count += 1
|
|
57
|
+
}
|
|
58
|
+
return count
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const list = async (credentialOptions, sub, params, fields) => {
|
|
62
|
+
const type = makeType(credentialOptions)
|
|
63
|
+
const credentials = await options.store.selectList(
|
|
64
|
+
options.table,
|
|
65
|
+
{
|
|
66
|
+
...params,
|
|
67
|
+
sub,
|
|
68
|
+
type
|
|
69
|
+
},
|
|
70
|
+
fields
|
|
71
|
+
)
|
|
72
|
+
// const now = nowInSeconds();
|
|
73
|
+
const list = []
|
|
74
|
+
for (let i = credentials.length; i--;) {
|
|
75
|
+
const credential = credentials[i]
|
|
76
|
+
// TODO need filter for expire
|
|
77
|
+
// if (credential.expire < now) {
|
|
78
|
+
// continue;
|
|
79
|
+
// }
|
|
80
|
+
const { encryptionKey: encryptedKey } = credential
|
|
81
|
+
delete credential.encryptionKey
|
|
82
|
+
const decryptedCredential = symetricDecryptFields(
|
|
83
|
+
credential,
|
|
84
|
+
{ encryptedKey, sub },
|
|
85
|
+
options.encryptedFields
|
|
86
|
+
)
|
|
87
|
+
list.push(decryptedCredential)
|
|
88
|
+
}
|
|
89
|
+
return list
|
|
90
|
+
}
|
|
91
|
+
|
|
19
92
|
export const create = async (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
93
|
+
credentialOptions,
|
|
94
|
+
sub,
|
|
95
|
+
{ id, value, ...values }
|
|
23
96
|
) => {
|
|
24
97
|
const now = nowInSeconds()
|
|
25
|
-
const type =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
98
|
+
const type = makeType(credentialOptions)
|
|
99
|
+
let { otp, expire } = credentialOptions
|
|
100
|
+
expire &&= now + expire
|
|
101
|
+
|
|
102
|
+
if (options.idGenerate) {
|
|
103
|
+
id ??= await options.randomId.create(options.idPrefix)
|
|
104
|
+
}
|
|
105
|
+
value ??= credentialOptions.create()
|
|
106
|
+
const encodedValue = await credentialOptions.encode(value)
|
|
107
|
+
|
|
108
|
+
const { encryptionKey, encryptedKey } = makeSymetricKey(sub)
|
|
109
|
+
const encryptedValues = symetricEncryptFields(
|
|
110
|
+
{ ...values, value: encodedValue },
|
|
111
|
+
{ encryptionKey, sub },
|
|
112
|
+
options.encryptedFields
|
|
36
113
|
)
|
|
37
114
|
const params = {
|
|
38
|
-
|
|
39
|
-
...rest,
|
|
115
|
+
...encryptedValues,
|
|
40
116
|
sub,
|
|
41
117
|
type,
|
|
42
118
|
otp,
|
|
43
119
|
encryptionKey: encryptedKey,
|
|
44
|
-
value: encryptedData,
|
|
45
120
|
create: now,
|
|
46
|
-
update: now
|
|
121
|
+
update: now,
|
|
122
|
+
expire
|
|
47
123
|
}
|
|
48
124
|
if (options.idGenerate) {
|
|
49
|
-
id ??= await options.randomId.create(options.idPrefix)
|
|
50
125
|
params.id = id
|
|
51
126
|
}
|
|
52
|
-
const
|
|
53
|
-
return
|
|
127
|
+
const row = await options.store.insert(options.table, params)
|
|
128
|
+
return { type, id: row.id, value, otp, expire }
|
|
54
129
|
}
|
|
55
130
|
|
|
56
131
|
export const update = async (
|
|
57
|
-
|
|
58
|
-
{ id, sub, encryptionKey, value, ...
|
|
59
|
-
parentOptions
|
|
132
|
+
credentialOptions,
|
|
133
|
+
{ id, sub, encryptionKey, encryptedKey, value, ...values }
|
|
60
134
|
) => {
|
|
61
135
|
const now = nowInSeconds()
|
|
62
|
-
const
|
|
136
|
+
// const type = makeType(credentialOptions);
|
|
137
|
+
|
|
138
|
+
const encryptedData = await credentialOptions.encode(
|
|
63
139
|
value,
|
|
64
140
|
encryptionKey,
|
|
141
|
+
encryptedKey,
|
|
65
142
|
sub
|
|
66
143
|
)
|
|
67
144
|
return options.store.update(
|
|
68
145
|
options.table,
|
|
69
|
-
{
|
|
146
|
+
{ sub, id },
|
|
70
147
|
{
|
|
71
|
-
...
|
|
148
|
+
...values,
|
|
72
149
|
value: encryptedData,
|
|
73
150
|
update: now
|
|
74
151
|
}
|
|
@@ -77,105 +154,124 @@ export const update = async (
|
|
|
77
154
|
|
|
78
155
|
export const subject = async (username) => {
|
|
79
156
|
return Promise.all(
|
|
80
|
-
options.usernameExists.map((exists) =>
|
|
157
|
+
options.usernameExists.map((exists) => {
|
|
158
|
+
return exists(username)
|
|
159
|
+
})
|
|
81
160
|
).then((identities) => {
|
|
82
161
|
return identities.filter((lookup) => lookup)?.[0]
|
|
83
162
|
})
|
|
84
163
|
}
|
|
85
164
|
|
|
86
|
-
export const authenticate = async (username, secret
|
|
87
|
-
const timeout = setTimeout(options.authenticationDuration)
|
|
88
|
-
const type = parentOptions.id + '-' + parentOptions.secret.type
|
|
89
|
-
|
|
165
|
+
export const authenticate = async (credentialOptions, username, secret) => {
|
|
90
166
|
const sub = await subject(username)
|
|
91
167
|
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
168
|
+
const timeout = setTimeout(options.authenticationDuration)
|
|
169
|
+
const type = makeType(credentialOptions)
|
|
170
|
+
|
|
171
|
+
const credentials = await options.store.selectList(
|
|
172
|
+
options.table,
|
|
173
|
+
{
|
|
174
|
+
sub,
|
|
175
|
+
type
|
|
176
|
+
},
|
|
177
|
+
['id', 'encryptionKey', 'value', 'otp', 'verify', 'expire', 'sourceId']
|
|
178
|
+
)
|
|
179
|
+
let valid
|
|
97
180
|
for (const credential of credentials) {
|
|
98
181
|
// non-opt credentials must be verified before use
|
|
99
|
-
if (!credential.otp && !credential.verify)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
182
|
+
if (!credential.otp && !credential.verify) {
|
|
183
|
+
continue
|
|
184
|
+
}
|
|
185
|
+
const { encryptionKey: encryptedKey } = credential
|
|
186
|
+
const decryptedCredential = symetricDecryptFields(
|
|
187
|
+
credential,
|
|
188
|
+
{ encryptedKey, sub },
|
|
189
|
+
options.encryptedFields
|
|
190
|
+
)
|
|
191
|
+
let { value, ...values } = decryptedCredential
|
|
192
|
+
value = await credentialOptions.decode(value)
|
|
193
|
+
valid = await credentialOptions.verify(secret, value, values)
|
|
103
194
|
if (valid) {
|
|
104
|
-
id
|
|
105
|
-
|
|
195
|
+
const { id, otp } = credential
|
|
196
|
+
if (otp) {
|
|
197
|
+
await options.store.remove(options.table, { id, sub })
|
|
198
|
+
} else if (credentialOptions.clean) {
|
|
199
|
+
await credentialOptions.clean(sub, value, values)
|
|
200
|
+
} else {
|
|
201
|
+
const now = nowInSeconds()
|
|
202
|
+
await options.store.update(
|
|
203
|
+
options.table,
|
|
204
|
+
{ id, sub },
|
|
205
|
+
{ update: now, lastused: now }
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
106
209
|
break
|
|
107
210
|
}
|
|
108
211
|
}
|
|
109
212
|
|
|
110
|
-
if (parentOptions.secret.otp) {
|
|
111
|
-
// delete OTP to prevent re-use
|
|
112
|
-
await options.store.remove(options.table, { id, sub })
|
|
113
|
-
} else if (valid && parentOptions.id !== 'WebAuthn') {
|
|
114
|
-
// WebAuthn has to update, skip here
|
|
115
|
-
const now = nowInSeconds()
|
|
116
|
-
await options.store.update(
|
|
117
|
-
options.table,
|
|
118
|
-
{ id, sub },
|
|
119
|
-
{ update: now, lastused: now }
|
|
120
|
-
)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
213
|
await timeout
|
|
124
214
|
if (!valid) throw new Error('401 Unauthorized')
|
|
125
|
-
return
|
|
215
|
+
return sub
|
|
126
216
|
}
|
|
127
217
|
|
|
128
|
-
export const verifySecret = async (sub, id
|
|
129
|
-
// const type =
|
|
218
|
+
export const verifySecret = async (credentialOptions, sub, id) => {
|
|
219
|
+
// const type = makeType(credentialOptions);
|
|
130
220
|
const now = nowInSeconds()
|
|
131
221
|
await options.store.update(
|
|
132
222
|
options.table,
|
|
133
|
-
{
|
|
223
|
+
{ sub, id },
|
|
134
224
|
{ update: now, verify: now }
|
|
135
225
|
)
|
|
136
226
|
}
|
|
137
227
|
|
|
138
|
-
export const verify = async (
|
|
228
|
+
export const verify = async (credentialOptions, sub, input) => {
|
|
139
229
|
const timeout = setTimeout(options.authenticationDuration)
|
|
140
|
-
const type =
|
|
141
|
-
|
|
230
|
+
const type = makeType(credentialOptions)
|
|
231
|
+
|
|
142
232
|
const credentials = await options.store.selectList(options.table, {
|
|
143
233
|
sub,
|
|
144
234
|
type
|
|
145
235
|
})
|
|
146
|
-
// TODO re-confirm when needed
|
|
147
|
-
// .then((rows) => {
|
|
148
|
-
// if (rows.length) {
|
|
149
|
-
// return rows
|
|
150
|
-
// }
|
|
151
|
-
//
|
|
152
|
-
// return options.store.select(options.table, { id: sub, type })
|
|
153
|
-
// })
|
|
154
236
|
|
|
155
|
-
let valid
|
|
156
|
-
for (
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
237
|
+
let valid, credential
|
|
238
|
+
for (credential of credentials) {
|
|
239
|
+
const { encryptionKey: encryptedKey } = credential
|
|
240
|
+
const decryptedCredential = symetricDecryptFields(
|
|
241
|
+
credential,
|
|
242
|
+
{ encryptedKey, sub },
|
|
243
|
+
options.encryptedFields
|
|
162
244
|
)
|
|
163
|
-
|
|
245
|
+
let { value, ...values } = decryptedCredential
|
|
246
|
+
value = await credentialOptions.decode(value)
|
|
247
|
+
valid = await credentialOptions.verify(input, value, values)
|
|
164
248
|
if (valid) {
|
|
165
|
-
id = credential
|
|
249
|
+
const { id, otp } = credential
|
|
250
|
+
if (otp) {
|
|
251
|
+
await options.store.remove(options.table, { id, sub })
|
|
252
|
+
}
|
|
253
|
+
|
|
166
254
|
break
|
|
167
255
|
}
|
|
168
256
|
}
|
|
169
|
-
|
|
170
|
-
await options.store.remove(options.table, { id, sub })
|
|
171
|
-
}
|
|
172
|
-
if (!valid) throw new Error('401 Unauthorized')
|
|
257
|
+
|
|
173
258
|
await timeout
|
|
174
|
-
|
|
259
|
+
if (!valid) throw new Error('401 Unauthorized')
|
|
260
|
+
return { ...credential, ...valid }
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export const expire = async (credentialOptions, sub, id) => {
|
|
264
|
+
// const type = makeType(credentialOptions);
|
|
265
|
+
await options.store.update(
|
|
266
|
+
options.table,
|
|
267
|
+
{ sub, id },
|
|
268
|
+
{ expire: nowInSeconds() - 1 }
|
|
269
|
+
)
|
|
175
270
|
}
|
|
176
271
|
|
|
177
|
-
export const
|
|
178
|
-
|
|
272
|
+
export const remove = async (credentialOptions, sub, id) => {
|
|
273
|
+
const type = makeType(credentialOptions)
|
|
274
|
+
await options.store.remove(options.table, { id, type, sub })
|
|
179
275
|
}
|
|
180
276
|
|
|
181
277
|
// TODO manage onboard state
|
|
@@ -184,4 +280,6 @@ export const expire = async (sub, id, parentOptions = options) => {
|
|
|
184
280
|
|
|
185
281
|
// TODO authorize management?
|
|
186
282
|
|
|
283
|
+
const makeType = (credentialOptions) =>
|
|
284
|
+
credentialOptions.id + '-' + credentialOptions.type
|
|
187
285
|
const nowInSeconds = () => Math.floor(Date.now() / 1000)
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1auth/authn",
|
|
3
|
-
"version": "0.0.0-alpha.
|
|
3
|
+
"version": "0.0.0-alpha.34",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
|
-
"node": ">=
|
|
7
|
+
"node": ">=20"
|
|
8
8
|
},
|
|
9
9
|
"engineStrict": true,
|
|
10
10
|
"publishConfig": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
],
|
|
28
28
|
"scripts": {
|
|
29
29
|
"test": "npm run test:unit",
|
|
30
|
-
"test:unit": "
|
|
30
|
+
"test:unit": "node --test"
|
|
31
31
|
},
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"funding": {
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"url": "https://github.com/willfarrell/1auth/issues"
|
|
49
49
|
},
|
|
50
50
|
"homepage": "https://github.com/willfarrell/1auth",
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "c88105a99efd7f3de80795736d6194e52ef465b4",
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@1auth/crypto": "0.0.0-alpha.
|
|
53
|
+
"@1auth/crypto": "0.0.0-alpha.34"
|
|
54
54
|
}
|
|
55
55
|
}
|