@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.
Files changed (2) hide show
  1. package/index.js +192 -94
  2. package/package.json +5 -5
package/index.js CHANGED
@@ -1,74 +1,151 @@
1
1
  import { setTimeout } from 'node:timers/promises'
2
- import { randomId, makeSymetricKey } from '@1auth/crypto'
2
+ import {
3
+ randomId,
4
+ makeSymetricKey,
5
+ symetricEncryptFields,
6
+ symetricDecryptFields
7
+ } from '@1auth/crypto'
3
8
 
4
- export const options = {
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: undefined,
11
- authenticationDuration: 500, // min duration authentication should take (ms)
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
- export default (params) => {
15
- Object.assign(options, { randomId }, params)
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
- credentialType,
21
- { id, sub, value, ...rest },
22
- parentOptions
93
+ credentialOptions,
94
+ sub,
95
+ { id, value, ...values }
23
96
  ) => {
24
97
  const now = nowInSeconds()
25
- const type = parentOptions.id + '-' + parentOptions[credentialType].type
26
- const otp = parentOptions[credentialType].otp
27
- const expire = parentOptions[credentialType].expire
28
- ? now + parentOptions[credentialType].expire
29
- : null
30
- const { encryptedKey } = makeSymetricKey(sub)
31
-
32
- const encryptedData = await parentOptions[credentialType].encode(
33
- value,
34
- encryptedKey,
35
- sub
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
- expire,
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 { id: serialId } = await options.store.insert(options.table, params)
53
- return serialId
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
- credentialType,
58
- { id, sub, encryptionKey, value, ...rest },
59
- parentOptions
132
+ credentialOptions,
133
+ { id, sub, encryptionKey, encryptedKey, value, ...values }
60
134
  ) => {
61
135
  const now = nowInSeconds()
62
- const encryptedData = await parentOptions[credentialType].encode(
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
- { id, sub },
146
+ { sub, id },
70
147
  {
71
- ...rest,
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) => exists(username))
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, parentOptions) => {
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 credentials = await options.store.selectList(options.table, {
93
- sub,
94
- type
95
- })
96
- let valid, id, encryptionKey
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) continue
100
- let { value, encryptionKey: encryptedKey, ...rest } = credential
101
- value = await parentOptions.secret.decode(value, encryptedKey, sub)
102
- valid = await parentOptions.secret.verify(secret, value, rest)
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 ??= credential.id
105
- encryptionKey ??= encryptedKey
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 { sub, id, encryptionKey, ...valid }
215
+ return sub
126
216
  }
127
217
 
128
- export const verifySecret = async (sub, id, parentOptions) => {
129
- // const type = parentOptions.id + '-' + parentOptions.secret.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
- { id, sub },
223
+ { sub, id },
134
224
  { update: now, verify: now }
135
225
  )
136
226
  }
137
227
 
138
- export const verify = async (credentialType, sub, token, parentOptions) => {
228
+ export const verify = async (credentialOptions, sub, input) => {
139
229
  const timeout = setTimeout(options.authenticationDuration)
140
- const type = parentOptions.id + '-' + parentOptions[credentialType].type
141
- let id
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 (const credential of credentials) {
157
- let { value, encryptionKey, ...rest } = credential
158
- value = await parentOptions[credentialType].decode(
159
- value,
160
- encryptionKey,
161
- sub
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
- valid = await parentOptions[credentialType].verify(token, value, rest)
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.id
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
- if (valid && parentOptions[credentialType].otp) {
170
- await options.store.remove(options.table, { id, sub })
171
- }
172
- if (!valid) throw new Error('401 Unauthorized')
257
+
173
258
  await timeout
174
- return { sub, id, ...valid }
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 expire = async (sub, id, parentOptions = options) => {
178
- await options.store.remove(options.table, { id, sub })
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.32",
3
+ "version": "0.0.0-alpha.34",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "engines": {
7
- "node": ">=16"
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": "ava"
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": "3750bef3d7e376c48f7d680e5f2181ee809213b9",
51
+ "gitHead": "c88105a99efd7f3de80795736d6194e52ef465b4",
52
52
  "dependencies": {
53
- "@1auth/crypto": "0.0.0-alpha.32"
53
+ "@1auth/crypto": "0.0.0-alpha.34"
54
54
  }
55
55
  }