@1auth/authn 0.0.0-alpha.5 → 0.0.0-alpha.50

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 (3) hide show
  1. package/index.js +225 -88
  2. package/package.json +5 -5
  3. package/LICENSE +0 -21
package/index.js CHANGED
@@ -1,68 +1,151 @@
1
1
  import { setTimeout } from 'node:timers/promises'
2
- import { randomId, makeSymetricKey } from '@1auth/crypto'
2
+ import {
3
+ randomId,
4
+ symmetricGenerateEncryptionKey,
5
+ symmetricEncryptFields,
6
+ symmetricDecryptFields
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
- table: 'credentials',
8
- authenticationDuration: 500, // min duration authentication should take (ms)
9
- usernameExists: [] // hooks to allow what to be used as a username
15
+ table: 'authentications',
16
+ idGenerate: true,
17
+ idPrefix: 'authn',
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']
10
22
  }
11
- export default (params) => {
12
- Object.assign(options, params)
23
+ const options = {}
24
+ export default (opt = {}) => {
25
+ Object.assign(options, defaults, opt)
13
26
  }
14
27
  export const getOptions = () => options
15
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 items = 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 = items.length; i--;) {
75
+ const item = items[i]
76
+ // TODO need filter for expire
77
+ // if (credential.expire < now) {
78
+ // continue;
79
+ // }
80
+ const { encryptionKey: encryptedKey } = item
81
+ delete item.encryptionKey
82
+ const decryptedItem = symmetricDecryptFields(
83
+ item,
84
+ { encryptedKey, sub },
85
+ options.encryptedFields
86
+ )
87
+ list.push(decryptedItem)
88
+ }
89
+ return list
90
+ }
91
+
16
92
  export const create = async (
17
- credentialType,
18
- { id, sub, value, ...rest },
19
- parentOptions
93
+ credentialOptions,
94
+ sub,
95
+ { id, value, ...values }
20
96
  ) => {
21
97
  const now = nowInSeconds()
22
- id ??= await randomId.create()
23
- const type = parentOptions.id + '-' + parentOptions[credentialType].type
24
- const otp = parentOptions[credentialType].otp
25
- const expire = parentOptions[credentialType].expire
26
- ? now + parentOptions[credentialType].expire
27
- : null
28
- const { encryptedKey } = makeSymetricKey(sub)
29
-
30
- const encryptedData = await parentOptions[credentialType].encode(
31
- value,
32
- encryptedKey,
33
- 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 } = symmetricGenerateEncryptionKey(sub)
109
+ const encryptedValues = symmetricEncryptFields(
110
+ { ...values, value: encodedValue },
111
+ { encryptionKey, sub },
112
+ options.encryptedFields
34
113
  )
35
- await options.store.insert(options.table, {
36
- expire,
37
- ...rest,
38
- id,
114
+ const params = {
115
+ ...encryptedValues,
39
116
  sub,
40
117
  type,
41
118
  otp,
42
119
  encryptionKey: encryptedKey,
43
- value: encryptedData,
44
120
  create: now,
45
- update: now
46
- })
47
- return id
121
+ update: now,
122
+ expire
123
+ }
124
+ if (options.idGenerate) {
125
+ params.id = id
126
+ }
127
+ const row = await options.store.insert(options.table, params)
128
+ return { type, id: row.id, value, otp, expire }
48
129
  }
49
130
 
50
131
  export const update = async (
51
- credentialType,
52
- { id, sub, encryptionKey, value, ...rest },
53
- parentOptions
132
+ credentialOptions,
133
+ { id, sub, encryptionKey, encryptedKey, value, ...values }
54
134
  ) => {
55
135
  const now = nowInSeconds()
56
- const encryptedData = await parentOptions[credentialType].encode(
136
+ // const type = makeType(credentialOptions);
137
+
138
+ const encryptedData = await credentialOptions.encode(
57
139
  value,
58
140
  encryptionKey,
141
+ encryptedKey,
59
142
  sub
60
143
  )
61
144
  return options.store.update(
62
145
  options.table,
63
- { id, sub },
146
+ { sub, id },
64
147
  {
65
- ...rest,
148
+ ...values,
66
149
  value: encryptedData,
67
150
  update: now
68
151
  }
@@ -71,92 +154,144 @@ export const update = async (
71
154
 
72
155
  export const subject = async (username) => {
73
156
  return Promise.all(
74
- options.usernameExists.map((exists) => exists(username))
157
+ options.usernameExists.map((exists) => {
158
+ return exists(username)
159
+ })
75
160
  ).then((identities) => {
76
161
  return identities.filter((lookup) => lookup)?.[0]
77
162
  })
78
163
  }
79
164
 
80
- export const authenticate = async (username, secret, parentOptions) => {
81
- const timeout = setTimeout(() => {}, options.authenticationDuration)
82
- const type = parentOptions.id + '-' + parentOptions.secret.type
83
-
165
+ export const authenticate = async (credentialOptions, username, secret) => {
84
166
  const sub = await subject(username)
85
167
 
86
- const credentials = await options.store.selectList(options.table, {
87
- sub,
88
- type
89
- }) // TODO and verify is not null
90
- 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
+ const now = nowInSeconds()
180
+ let valid
91
181
  for (const credential of credentials) {
92
- let { value, encryptionKey: encryptedKey, ...rest } = credential
93
- value = await parentOptions.secret.decode(value, encryptedKey, sub)
94
- valid = await parentOptions.secret.verify(secret, value, rest)
182
+ // non-opt credentials must be verified before use
183
+ if (!credential.otp && !credential.verify) {
184
+ continue
185
+ }
186
+ const { encryptionKey: encryptedKey } = credential
187
+ const decryptedCredential = symmetricDecryptFields(
188
+ credential,
189
+ { encryptedKey, sub },
190
+ options.encryptedFields
191
+ )
192
+ let { value, ...values } = decryptedCredential
193
+ value = await credentialOptions.decode(value)
194
+ valid = await credentialOptions.verify(secret, value, values)
95
195
  if (valid) {
96
- id ??= credential.id
97
- encryptionKey ??= encryptedKey
196
+ const { id, otp } = credential
197
+ if (otp) {
198
+ await options.store.update(
199
+ options.table,
200
+ { id, sub },
201
+ { update: now, expire: now, lastused: now }
202
+ )
203
+ } else if (credentialOptions.clean) {
204
+ await credentialOptions.clean(sub, value, values)
205
+ } else {
206
+ const now = nowInSeconds()
207
+ await options.store.update(
208
+ options.table,
209
+ { id, sub },
210
+ { update: now, lastused: now }
211
+ )
212
+ }
213
+
98
214
  break
99
215
  }
100
216
  }
101
217
 
102
- if (valid && parentOptions.secret.otp) {
103
- await options.store.remove(options.table, { id, sub })
104
- }
105
218
  await timeout
106
- if (!valid) throw new Error('401 Unauthorized')
107
- return { sub, id, encryptionKey, ...valid }
219
+ if (!valid) {
220
+ throw new Error('401 Unauthorized')
221
+ }
222
+ return sub
108
223
  }
109
224
 
110
- export const verifySecret = async (sub, id, parentOptions) => {
111
- // const type = parentOptions.id + '-' + parentOptions.secret.type
225
+ export const verifySecret = async (credentialOptions, sub, id) => {
226
+ // const type = makeType(credentialOptions);
227
+ const now = nowInSeconds()
112
228
  await options.store.update(
113
229
  options.table,
114
- { id, sub },
115
- { verify: nowInSeconds() }
230
+ { sub, id },
231
+ { update: now, verify: now }
116
232
  )
117
233
  }
118
234
 
119
- export const verify = async (credentialType, sub, token, parentOptions) => {
120
- const timeout = setTimeout(() => {}, options.authenticationDuration)
121
- const type = parentOptions.id + '-' + parentOptions[credentialType].type
122
- let id
235
+ export const verify = async (credentialOptions, sub, input) => {
236
+ const timeout = setTimeout(options.authenticationDuration)
237
+ const type = makeType(credentialOptions)
238
+
123
239
  const credentials = await options.store.selectList(options.table, {
124
240
  sub,
125
241
  type
126
242
  })
127
- // TODO re-confirm when needed
128
- // .then((rows) => {
129
- // if (rows.length) {
130
- // return rows
131
- // }
132
- //
133
- // return options.store.select(options.table, { id: sub, type })
134
- // })
135
243
 
136
- let valid
137
- for (const credential of credentials) {
138
- let { value, encryptionKey, ...rest } = credential
139
- value = await parentOptions[credentialType].decode(
140
- value,
141
- encryptionKey,
142
- sub
244
+ let valid, credential
245
+ for (credential of credentials) {
246
+ const { encryptionKey: encryptedKey } = credential
247
+ const decryptedCredential = symmetricDecryptFields(
248
+ credential,
249
+ { encryptedKey, sub },
250
+ options.encryptedFields
143
251
  )
144
- valid = await parentOptions[credentialType].verify(token, value, rest)
252
+ let { value, ...values } = decryptedCredential
253
+ value = await credentialOptions.decode(value)
254
+ valid = await credentialOptions.verify(input, value, values)
145
255
  if (valid) {
146
- id = credential.id
256
+ const { id, otp } = credential
257
+ if (otp) {
258
+ await options.store.remove(options.table, { id, sub })
259
+ }
260
+
147
261
  break
148
262
  }
149
263
  }
150
- if (valid && parentOptions[credentialType].otp) {
151
- await options.store.remove(options.table, { id, sub })
152
- }
153
- if (!valid) throw new Error('401 Unauthorized')
264
+
154
265
  await timeout
155
- return { sub, id, ...valid }
266
+ if (!valid) throw new Error('401 Unauthorized')
267
+ return { ...credential, ...valid }
156
268
  }
157
269
 
158
- export const expire = async (sub, id, parentOptions = options) => {
159
- await options.store.remove(options.table, { id, sub })
270
+ export const expire = async (credentialOptions, sub, id) => {
271
+ // const type = makeType(credentialOptions);
272
+ await options.store.update(
273
+ options.table,
274
+ { sub, id },
275
+ { expire: nowInSeconds() - 1 }
276
+ )
277
+ }
278
+
279
+ export const remove = async (credentialOptions, sub, id) => {
280
+ const type = makeType(credentialOptions)
281
+ await options.store.remove(options.table, { id, type, sub })
282
+ }
283
+
284
+ export const select = async (credentialOptions, sub, id) => {
285
+ const type = makeType(credentialOptions)
286
+ const item = await options.store.select(options.table, { id, type, sub })
287
+ const { encryptionKey: encryptedKey } = item
288
+ delete item.encryptionKey
289
+ const decryptedItem = symmetricDecryptFields(
290
+ item,
291
+ { encryptedKey, sub },
292
+ options.encryptedFields
293
+ )
294
+ return decryptedItem
160
295
  }
161
296
 
162
297
  // TODO manage onboard state
@@ -165,4 +300,6 @@ export const expire = async (sub, id, parentOptions = options) => {
165
300
 
166
301
  // TODO authorize management?
167
302
 
303
+ const makeType = (credentialOptions) =>
304
+ credentialOptions.id + '-' + credentialOptions.type
168
305
  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.5",
3
+ "version": "0.0.0-alpha.50",
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": "32bb45598025fe664497e4ce8b9c1cf41c75d635",
51
+ "gitHead": "7a6c0fbb8ab71d6a2171e678697de9f237568431",
52
52
  "dependencies": {
53
- "@1auth/crypto": "0.0.0-alpha.5"
53
+ "@1auth/crypto": "0.0.0-alpha.50"
54
54
  }
55
55
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2023 will Farrell
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.