@1auth/authn 0.0.0-alpha.4 → 0.0.0-alpha.41

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 +204 -87
  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
+ 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
- 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 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
+
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 } = makeSymetricKey(sub)
109
+ const encryptedValues = symetricEncryptFields(
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,124 @@ 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
+ let valid
91
180
  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)
181
+ // non-opt credentials must be verified before use
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)
95
194
  if (valid) {
96
- id ??= credential.id
97
- 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
+
98
209
  break
99
210
  }
100
211
  }
101
212
 
102
- if (valid && parentOptions.secret.otp) {
103
- await options.store.remove(options.table, { id, sub })
104
- }
105
213
  await timeout
106
214
  if (!valid) throw new Error('401 Unauthorized')
107
- return { sub, id, encryptionKey, ...valid }
215
+ return sub
108
216
  }
109
217
 
110
- export const verifySecret = async (sub, id, parentOptions) => {
111
- // const type = parentOptions.id + '-' + parentOptions.secret.type
218
+ export const verifySecret = async (credentialOptions, sub, id) => {
219
+ // const type = makeType(credentialOptions);
220
+ const now = nowInSeconds()
112
221
  await options.store.update(
113
222
  options.table,
114
- { id, sub },
115
- { verify: nowInSeconds() }
223
+ { sub, id },
224
+ { update: now, verify: now }
116
225
  )
117
226
  }
118
227
 
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
228
+ export const verify = async (credentialOptions, sub, input) => {
229
+ const timeout = setTimeout(options.authenticationDuration)
230
+ const type = makeType(credentialOptions)
231
+
123
232
  const credentials = await options.store.selectList(options.table, {
124
233
  sub,
125
234
  type
126
235
  })
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
236
 
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
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
143
244
  )
144
- 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)
145
248
  if (valid) {
146
- id = credential.id
249
+ const { id, otp } = credential
250
+ if (otp) {
251
+ await options.store.remove(options.table, { id, sub })
252
+ }
253
+
147
254
  break
148
255
  }
149
256
  }
150
- if (valid && parentOptions[credentialType].otp) {
151
- await options.store.remove(options.table, { id, sub })
152
- }
153
- if (!valid) throw new Error('401 Unauthorized')
257
+
154
258
  await timeout
155
- 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
+ )
156
270
  }
157
271
 
158
- export const expire = async (sub, id, parentOptions = options) => {
159
- 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 })
160
275
  }
161
276
 
162
277
  // TODO manage onboard state
@@ -165,4 +280,6 @@ export const expire = async (sub, id, parentOptions = options) => {
165
280
 
166
281
  // TODO authorize management?
167
282
 
283
+ const makeType = (credentialOptions) =>
284
+ credentialOptions.id + '-' + credentialOptions.type
168
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.4",
3
+ "version": "0.0.0-alpha.41",
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": "47894073ee5f1b6754cef5e482f123dc81575b1c",
51
+ "gitHead": "7a6c0fbb8ab71d6a2171e678697de9f237568431",
52
52
  "dependencies": {
53
- "@1auth/crypto": "0.0.0-alpha.4"
53
+ "@1auth/crypto": "0.0.0-alpha.41"
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.