@furystack/rest-service 11.0.7 → 12.0.0

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 (74) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +3 -2
  3. package/esm/authenticate.d.ts.map +1 -1
  4. package/esm/authenticate.js +4 -1
  5. package/esm/authenticate.js.map +1 -1
  6. package/esm/authenticate.spec.js +6 -4
  7. package/esm/authenticate.spec.js.map +1 -1
  8. package/esm/authentication-providers/authentication-provider.d.ts +25 -0
  9. package/esm/authentication-providers/authentication-provider.d.ts.map +1 -0
  10. package/esm/authentication-providers/authentication-provider.js +2 -0
  11. package/esm/authentication-providers/authentication-provider.js.map +1 -0
  12. package/esm/authentication-providers/basic-auth-provider.d.ts +8 -0
  13. package/esm/authentication-providers/basic-auth-provider.d.ts.map +1 -0
  14. package/esm/authentication-providers/basic-auth-provider.js +18 -0
  15. package/esm/authentication-providers/basic-auth-provider.js.map +1 -0
  16. package/esm/authentication-providers/cookie-auth-provider.d.ts +11 -0
  17. package/esm/authentication-providers/cookie-auth-provider.d.ts.map +1 -0
  18. package/esm/authentication-providers/cookie-auth-provider.js +21 -0
  19. package/esm/authentication-providers/cookie-auth-provider.js.map +1 -0
  20. package/esm/authentication-providers/helpers.d.ts +11 -0
  21. package/esm/authentication-providers/helpers.d.ts.map +1 -0
  22. package/esm/authentication-providers/helpers.js +47 -0
  23. package/esm/authentication-providers/helpers.js.map +1 -0
  24. package/esm/authentication-providers/index.d.ts +5 -0
  25. package/esm/authentication-providers/index.d.ts.map +1 -0
  26. package/esm/authentication-providers/index.js +5 -0
  27. package/esm/authentication-providers/index.js.map +1 -0
  28. package/esm/endpoint-generators/utils.d.ts.map +1 -1
  29. package/esm/endpoint-generators/utils.js +4 -1
  30. package/esm/endpoint-generators/utils.js.map +1 -1
  31. package/esm/helpers.d.ts +5 -2
  32. package/esm/helpers.d.ts.map +1 -1
  33. package/esm/helpers.js +27 -3
  34. package/esm/helpers.js.map +1 -1
  35. package/esm/helpers.spec.js +37 -0
  36. package/esm/helpers.spec.js.map +1 -1
  37. package/esm/http-authentication-settings.d.ts +11 -4
  38. package/esm/http-authentication-settings.d.ts.map +1 -1
  39. package/esm/http-authentication-settings.js +9 -2
  40. package/esm/http-authentication-settings.js.map +1 -1
  41. package/esm/http-user-context.d.ts +9 -4
  42. package/esm/http-user-context.d.ts.map +1 -1
  43. package/esm/http-user-context.js +28 -55
  44. package/esm/http-user-context.js.map +1 -1
  45. package/esm/http-user-context.spec.d.ts +3 -1
  46. package/esm/http-user-context.spec.d.ts.map +1 -1
  47. package/esm/http-user-context.spec.js +103 -45
  48. package/esm/http-user-context.spec.js.map +1 -1
  49. package/esm/index.d.ts +1 -0
  50. package/esm/index.d.ts.map +1 -1
  51. package/esm/index.js +1 -0
  52. package/esm/index.js.map +1 -1
  53. package/esm/rest-service.integration.spec.d.ts.map +1 -1
  54. package/esm/rest-service.integration.spec.js +5 -4
  55. package/esm/rest-service.integration.spec.js.map +1 -1
  56. package/esm/validate.integration.spec.js +5 -0
  57. package/esm/validate.integration.spec.js.map +1 -1
  58. package/package.json +6 -6
  59. package/src/authenticate.spec.ts +6 -4
  60. package/src/authenticate.ts +4 -1
  61. package/src/authentication-providers/authentication-provider.ts +25 -0
  62. package/src/authentication-providers/basic-auth-provider.ts +21 -0
  63. package/src/authentication-providers/cookie-auth-provider.ts +26 -0
  64. package/src/authentication-providers/helpers.ts +73 -0
  65. package/src/authentication-providers/index.ts +4 -0
  66. package/src/endpoint-generators/utils.ts +4 -1
  67. package/src/helpers.spec.ts +40 -0
  68. package/src/helpers.ts +48 -3
  69. package/src/http-authentication-settings.ts +14 -5
  70. package/src/http-user-context.spec.ts +112 -44
  71. package/src/http-user-context.ts +27 -57
  72. package/src/index.ts +1 -0
  73. package/src/rest-service.integration.spec.ts +5 -4
  74. package/src/validate.integration.spec.ts +5 -0
@@ -1,7 +1,14 @@
1
1
  /* eslint-disable @typescript-eslint/ban-ts-comment */
2
- import { InMemoryStore, StoreManager, User, addStore } from '@furystack/core'
2
+ import { InMemoryStore, StoreManager, User, addStore, useSystemIdentityContext } from '@furystack/core'
3
3
  import { Injector } from '@furystack/inject'
4
- import { PasswordAuthenticator, PasswordCredential, UnauthenticatedError } from '@furystack/security'
4
+ import { getDataSetFor, getRepository } from '@furystack/repository'
5
+ import {
6
+ PasswordAuthenticator,
7
+ PasswordCredential,
8
+ PasswordResetToken,
9
+ UnauthenticatedError,
10
+ usePasswordPolicy,
11
+ } from '@furystack/security'
5
12
  import { usingAsync } from '@furystack/utils'
6
13
  import type { IncomingMessage, ServerResponse } from 'http'
7
14
  import { describe, expect, it, vi } from 'vitest'
@@ -9,12 +16,20 @@ import { useHttpAuthentication } from './helpers.js'
9
16
  import { HttpUserContext } from './http-user-context.js'
10
17
  import { DefaultSession } from './models/default-session.js'
11
18
 
12
- export const prepareInjector = async (i: Injector) => {
19
+ export const prepareInjector = async (i: Injector, options?: { enableBasicAuth?: boolean }) => {
13
20
  addStore(i, new InMemoryStore({ model: User, primaryKey: 'username' }))
14
21
  .addStore(new InMemoryStore({ model: DefaultSession, primaryKey: 'sessionId' }))
15
22
  .addStore(new InMemoryStore({ model: PasswordCredential, primaryKey: 'userName' }))
23
+ .addStore(new InMemoryStore({ model: PasswordResetToken, primaryKey: 'token' }))
16
24
 
17
- useHttpAuthentication(i)
25
+ const repo = getRepository(i)
26
+ repo.createDataSet(User, 'username')
27
+ repo.createDataSet(DefaultSession, 'sessionId')
28
+ repo.createDataSet(PasswordCredential, 'userName')
29
+ repo.createDataSet(PasswordResetToken, 'token')
30
+
31
+ usePasswordPolicy(i)
32
+ useHttpAuthentication(i, { enableBasicAuth: options?.enableBasicAuth ?? true })
18
33
  }
19
34
 
20
35
  const setupUser = async (i: Injector, userName: string, password: string) => {
@@ -184,31 +199,28 @@ describe('HttpUserContext', () => {
184
199
  })
185
200
 
186
201
  describe('authenticateRequest', () => {
187
- it('Should try to authenticate with Basic, if enabled', async () => {
202
+ it('Should authenticate with Basic Auth when enabled and valid credentials provided', async () => {
188
203
  await usingAsync(new Injector(), async (i) => {
189
204
  await prepareInjector(i)
205
+ await setupUser(i, 'testuser', 'password')
190
206
  const ctx = i.getInstance(HttpUserContext)
191
- ctx.authenticateUser = vi.fn(async () => testUser)
192
207
  const result = await ctx.authenticateRequest({
193
208
  headers: { authorization: `Basic dGVzdHVzZXI6cGFzc3dvcmQ=` },
194
209
  } as IncomingMessage)
195
- expect(ctx.authenticateUser).toBeCalledWith('testuser', 'password')
196
- expect(result).toBe(testUser)
210
+ expect(result.username).toBe('testuser')
197
211
  })
198
212
  })
199
213
 
200
- it('Should NOT try to authenticate with Basic, if disabled', async () => {
214
+ it('Should NOT try to authenticate with Basic when disabled', async () => {
201
215
  await usingAsync(new Injector(), async (i) => {
202
- await prepareInjector(i)
216
+ await prepareInjector(i, { enableBasicAuth: false })
217
+ await setupUser(i, 'testuser', 'password')
203
218
  const ctx = i.getInstance(HttpUserContext)
204
- ctx.authentication.enableBasicAuth = false
205
- ctx.authenticateUser = vi.fn(async () => testUser)
206
219
  await expect(
207
220
  ctx.authenticateRequest({
208
221
  headers: { authorization: `Basic dGVzdHVzZXI6cGFzc3dvcmQ=` },
209
222
  } as IncomingMessage),
210
223
  ).rejects.toThrowError(UnauthenticatedError)
211
- expect(ctx.authenticateUser).not.toBeCalled()
212
224
  })
213
225
  })
214
226
 
@@ -228,9 +240,10 @@ describe('HttpUserContext', () => {
228
240
  await usingAsync(new Injector(), async (i) => {
229
241
  await prepareInjector(i)
230
242
  const ctx = i.getInstance(HttpUserContext)
231
- await ctx.authentication
232
- .getSessionStore(i.getInstance(StoreManager))
233
- .add({ sessionId: '666', username: testUser.username })
243
+ await i.getInstance(StoreManager).getStoreFor(DefaultSession, 'sessionId').add({
244
+ sessionId: '666',
245
+ username: testUser.username,
246
+ })
234
247
  await expect(
235
248
  ctx.authenticateRequest({
236
249
  headers: { cookie: `${ctx.authentication.cookieName}=666;a=3` },
@@ -244,11 +257,15 @@ describe('HttpUserContext', () => {
244
257
  await prepareInjector(i)
245
258
 
246
259
  const ctx = i.getInstance(HttpUserContext)
247
- await ctx.authentication
248
- .getSessionStore(i.getInstance(StoreManager))
249
- .add({ sessionId: '666', username: testUser.username })
260
+ await i.getInstance(StoreManager).getStoreFor(DefaultSession, 'sessionId').add({
261
+ sessionId: '666',
262
+ username: testUser.username,
263
+ })
250
264
 
251
- await ctx.authentication.getUserStore(i.getInstance(StoreManager)).add({ ...testUser })
265
+ await i
266
+ .getInstance(StoreManager)
267
+ .getStoreFor(User, 'username')
268
+ .add({ ...testUser })
252
269
 
253
270
  const result = await ctx.authenticateRequest({
254
271
  headers: { cookie: `${ctx.authentication.cookieName}=666;a=3` },
@@ -257,6 +274,49 @@ describe('HttpUserContext', () => {
257
274
  expect(result).toEqual(testUser)
258
275
  })
259
276
  })
277
+
278
+ it('Should iterate providers and return null results pass to next', async () => {
279
+ await usingAsync(new Injector(), async (i) => {
280
+ await prepareInjector(i)
281
+ const ctx = i.getInstance(HttpUserContext)
282
+ const provider1 = vi.fn(async () => null)
283
+ const provider2 = vi.fn(async () => testUser)
284
+ ctx.authentication.authenticationProviders = [
285
+ { name: 'test-1', authenticate: provider1 },
286
+ { name: 'test-2', authenticate: provider2 },
287
+ ]
288
+ const result = await ctx.authenticateRequest(request)
289
+ expect(provider1).toHaveBeenCalledOnce()
290
+ expect(provider2).toHaveBeenCalledOnce()
291
+ expect(result).toBe(testUser)
292
+ })
293
+ })
294
+
295
+ it('Should throw if provider throws (skipping remaining providers)', async () => {
296
+ await usingAsync(new Injector(), async (i) => {
297
+ await prepareInjector(i)
298
+ const ctx = i.getInstance(HttpUserContext)
299
+ const provider1 = vi.fn(async () => {
300
+ throw new UnauthenticatedError()
301
+ })
302
+ const provider2 = vi.fn(async () => testUser)
303
+ ctx.authentication.authenticationProviders = [
304
+ { name: 'test-1', authenticate: provider1 },
305
+ { name: 'test-2', authenticate: provider2 },
306
+ ]
307
+ await expect(ctx.authenticateRequest(request)).rejects.toThrowError(UnauthenticatedError)
308
+ expect(provider2).not.toHaveBeenCalled()
309
+ })
310
+ })
311
+
312
+ it('Should throw UnauthenticatedError if no provider returns a user', async () => {
313
+ await usingAsync(new Injector(), async (i) => {
314
+ await prepareInjector(i)
315
+ const ctx = i.getInstance(HttpUserContext)
316
+ ctx.authentication.authenticationProviders = [{ name: 'test-1', authenticate: async () => null }]
317
+ await expect(ctx.authenticateRequest(request)).rejects.toThrowError(UnauthenticatedError)
318
+ })
319
+ })
260
320
  })
261
321
 
262
322
  describe('getCurrentUser', () => {
@@ -280,14 +340,13 @@ describe('HttpUserContext', () => {
280
340
  await prepareInjector(i)
281
341
  const ctx = i.getInstance(HttpUserContext)
282
342
  const setHeader = vi.fn()
343
+ const addMock = vi.fn(async () => ({}))
283
344
  // @ts-expect-error
284
- ctx.getSessionStore().add = vi.fn(async () => {
285
- return {}
286
- })
345
+ ctx.getSessionDataSet = vi.fn(() => ({ add: addMock }))
287
346
  const authResult = await ctx.cookieLogin(testUser, { setHeader })
288
347
  expect(authResult).toBe(testUser)
289
348
  expect(setHeader).toBeCalled()
290
- expect(ctx.getSessionStore().add).toBeCalled()
349
+ expect(addMock).toBeCalled()
291
350
  })
292
351
  })
293
352
  })
@@ -298,18 +357,22 @@ describe('HttpUserContext', () => {
298
357
  await prepareInjector(i)
299
358
  const ctx = i.getInstance(HttpUserContext)
300
359
  const setHeader = vi.fn()
360
+ const removeMock = vi.fn(async () => undefined)
361
+ const sessionDataSetMock = {
362
+ add: vi.fn(async () => ({})),
363
+ find: vi.fn(async () => [{ sessionId: 'example-session-id' }]),
364
+ remove: removeMock,
365
+ primaryKey: 'sessionId' as const,
366
+ }
301
367
  // @ts-expect-error
302
- ctx.getSessionStore().add = vi.fn(async () => {
303
- return {}
304
- })
368
+ ctx.getSessionDataSet = vi.fn(() => sessionDataSetMock)
305
369
  ctx.authenticateRequest = vi.fn(async () => testUser)
306
- ctx.getSessionStore().remove = vi.fn(async () => undefined)
307
370
  ctx.getSessionIdFromRequest = () => 'example-session-id'
308
371
  response.setHeader = vi.fn(() => response)
309
372
  await ctx.cookieLogin(testUser, { setHeader })
310
373
  await ctx.cookieLogout(request, response)
311
374
  expect(response.setHeader).toBeCalledWith('Set-Cookie', 'fss=; Path=/; HttpOnly')
312
- expect(ctx.getSessionStore().remove).toBeCalled()
375
+ expect(removeMock).toBeCalled()
313
376
  })
314
377
  })
315
378
  })
@@ -319,23 +382,25 @@ describe('HttpUserContext', () => {
319
382
  return usingAsync(new Injector(), async (i) => {
320
383
  await prepareInjector(i)
321
384
  const ctx = i.getInstance(HttpUserContext)
322
- const userStore = i.getInstance(StoreManager).getStoreFor(User, 'username')
323
- await userStore.add(testUser)
385
+ const sm = i.getInstance(StoreManager)
386
+ await sm.getStoreFor(User, 'username').add(testUser)
324
387
 
325
388
  const pw = await i.getInstance(PasswordAuthenticator).hasher.createCredential(testUser.username, 'test')
326
- await i.getInstance(StoreManager).getStoreFor(PasswordCredential, 'userName').add(pw)
389
+ await sm.getStoreFor(PasswordCredential, 'userName').add(pw)
327
390
 
328
391
  await ctx.cookieLogin(testUser, { setHeader: vi.fn() })
329
392
 
330
393
  const originalUser = await ctx.getCurrentUser(request)
331
394
  expect(originalUser).toEqual(testUser)
332
395
 
396
+ const systemInjector = useSystemIdentityContext({ injector: i, username: 'test' })
397
+ const userDataSet = getDataSetFor(systemInjector, User, 'username')
333
398
  const updatedUser = { ...testUser, roles: ['newFancyRole'] }
334
- await userStore.update(testUser.username, updatedUser)
399
+ await userDataSet.update(systemInjector, testUser.username, updatedUser)
335
400
  const updatedUserFromContext = await ctx.getCurrentUser(request)
336
401
  expect(updatedUserFromContext.roles).toEqual(['newFancyRole'])
337
402
 
338
- await userStore.update(testUser.username, { ...updatedUser, roles: [] })
403
+ await userDataSet.update(systemInjector, testUser.username, { ...updatedUser, roles: [] })
339
404
  const reloadedUserFromContext = await ctx.getCurrentUser(request)
340
405
  expect(reloadedUserFromContext.roles).toEqual([])
341
406
  })
@@ -345,18 +410,20 @@ describe('HttpUserContext', () => {
345
410
  return usingAsync(new Injector(), async (i) => {
346
411
  await prepareInjector(i)
347
412
  const ctx = i.getInstance(HttpUserContext)
348
- const userStore = i.getInstance(StoreManager).getStoreFor(User, 'username')
349
- await userStore.add(testUser)
413
+ const sm = i.getInstance(StoreManager)
414
+ await sm.getStoreFor(User, 'username').add(testUser)
350
415
 
351
416
  const pw = await i.getInstance(PasswordAuthenticator).hasher.createCredential(testUser.username, 'test')
352
- await i.getInstance(StoreManager).getStoreFor(PasswordCredential, 'userName').add(pw)
417
+ await sm.getStoreFor(PasswordCredential, 'userName').add(pw)
353
418
 
354
419
  await ctx.cookieLogin(testUser, { setHeader: vi.fn() })
355
420
 
356
421
  const originalUser = await ctx.getCurrentUser(request)
357
422
  expect(originalUser).toEqual(testUser)
358
423
 
359
- await userStore.remove(testUser.username)
424
+ const systemInjector = useSystemIdentityContext({ injector: i, username: 'test' })
425
+ const userDataSet = getDataSetFor(systemInjector, User, 'username')
426
+ await userDataSet.remove(systemInjector, testUser.username)
360
427
 
361
428
  await expect(() => ctx.getCurrentUser(request)).rejects.toThrowError(UnauthenticatedError)
362
429
  })
@@ -366,17 +433,17 @@ describe('HttpUserContext', () => {
366
433
  return usingAsync(new Injector(), async (i) => {
367
434
  await prepareInjector(i)
368
435
  const ctx = i.getInstance(HttpUserContext)
369
- const userStore = i.getInstance(StoreManager).getStoreFor(User, 'username')
370
- await userStore.add(testUser)
436
+ const sm = i.getInstance(StoreManager)
437
+ await sm.getStoreFor(User, 'username').add(testUser)
371
438
 
372
439
  let sessionId = ''
373
440
 
374
441
  const pw = await i.getInstance(PasswordAuthenticator).hasher.createCredential(testUser.username, 'test')
375
- await i.getInstance(StoreManager).getStoreFor(PasswordCredential, 'userName').add(pw)
442
+ await sm.getStoreFor(PasswordCredential, 'userName').add(pw)
376
443
 
377
444
  await ctx.cookieLogin(testUser, {
378
445
  setHeader: (_headerName, headerValue) => {
379
- sessionId = headerValue
446
+ sessionId = headerValue.split('=')[1].split(';')[0]
380
447
  return {} as ServerResponse
381
448
  },
382
449
  })
@@ -384,8 +451,9 @@ describe('HttpUserContext', () => {
384
451
  const originalUser = await ctx.getCurrentUser(request)
385
452
  expect(originalUser).toEqual(testUser)
386
453
 
387
- const sessionStore = ctx.getSessionStore()
388
- await sessionStore.remove(sessionId)
454
+ const systemInjector = useSystemIdentityContext({ injector: i, username: 'test' })
455
+ const sessionDataSet = getDataSetFor(systemInjector, DefaultSession, 'sessionId')
456
+ await sessionDataSet.remove(systemInjector, sessionId)
389
457
 
390
458
  await expect(() => ctx.getCurrentUser(request)).rejects.toThrowError(UnauthenticatedError)
391
459
  })
@@ -1,9 +1,11 @@
1
1
  import type { User } from '@furystack/core'
2
- import { StoreManager } from '@furystack/core'
2
+ import { useSystemIdentityContext } from '@furystack/core'
3
+ import type { Injector } from '@furystack/inject'
3
4
  import { Injectable, Injected } from '@furystack/inject'
4
5
  import { PasswordAuthenticator, UnauthenticatedError } from '@furystack/security'
5
6
  import { randomBytes } from 'crypto'
6
7
  import type { IncomingMessage } from 'http'
8
+ import { extractSessionIdFromCookies } from './authentication-providers/helpers.js'
7
9
  import { HttpAuthenticationSettings } from './http-authentication-settings.js'
8
10
  import type { DefaultSession } from './models/default-session.js'
9
11
 
@@ -12,28 +14,19 @@ import type { DefaultSession } from './models/default-session.js'
12
14
  */
13
15
  @Injectable({ lifetime: 'scoped' })
14
16
  export class HttpUserContext {
15
- public getUserStore = () => this.authentication.getUserStore(this.storeManager)
17
+ public getUserDataSet = () => this.authentication.getUserDataSet(this.systemInjector)
16
18
 
17
- public getSessionStore = () => this.authentication.getSessionStore(this.storeManager)
19
+ public getSessionDataSet = () => this.authentication.getSessionDataSet(this.systemInjector)
18
20
 
19
21
  private getUserByName = async (userName: string) => {
20
- const userStore = this.getUserStore()
21
- const users = await userStore.find({ filter: { username: { $eq: userName } }, top: 2 })
22
+ const userDataSet = this.getUserDataSet()
23
+ const users = await userDataSet.find(this.systemInjector, { filter: { username: { $eq: userName } }, top: 2 })
22
24
  if (users.length !== 1) {
23
25
  throw new UnauthenticatedError()
24
26
  }
25
27
  return users[0]
26
28
  }
27
29
 
28
- private getSessionById = async (sessionId: string) => {
29
- const sessionStore = this.getSessionStore()
30
- const sessions = await sessionStore.find({ filter: { sessionId: { $eq: sessionId } }, top: 2 })
31
- if (sessions.length !== 1) {
32
- throw new UnauthenticatedError()
33
- }
34
- return sessions[0]
35
- }
36
-
37
30
  private user?: User
38
31
 
39
32
  /**
@@ -93,43 +86,20 @@ export class HttpUserContext {
93
86
  }
94
87
 
95
88
  public getSessionIdFromRequest(request: Pick<IncomingMessage, 'headers'>): string | null {
96
- if (request.headers.cookie) {
97
- const cookies = request.headers.cookie
98
- .toString()
99
- .split(';')
100
- .filter((val) => val.length > 0)
101
- .map((val) => {
102
- const [name, value] = val.split('=')
103
- return { name: name?.trim(), value: value?.trim() }
104
- })
105
- const sessionCookie = cookies.find((c) => c.name === this.authentication.cookieName)
106
- if (sessionCookie) {
107
- return sessionCookie.value
108
- }
109
- }
110
- return null
89
+ return extractSessionIdFromCookies(request, this.authentication.cookieName)
111
90
  }
112
91
 
92
+ /**
93
+ * Iterates registered authentication providers in order.
94
+ * - A provider returning `User` means authentication succeeded.
95
+ * - A provider returning `null` means it does not apply; try the next one.
96
+ * - A provider throwing means it owns the request but auth failed; propagate the error.
97
+ */
113
98
  public async authenticateRequest(request: Pick<IncomingMessage, 'headers'>): Promise<User> {
114
- // Basic auth
115
- if (this.authentication.enableBasicAuth && request.headers.authorization) {
116
- const authData = Buffer.from(request.headers.authorization.toString().split(' ')[1], 'base64')
117
- const [userName, password] = authData.toString().split(':')
118
- return await this.authenticateUser(userName, password)
99
+ for (const provider of this.authentication.authenticationProviders) {
100
+ const user = await provider.authenticate(request)
101
+ if (user) return user
119
102
  }
120
-
121
- // Cookie auth
122
- const sessionId = this.getSessionIdFromRequest(request)
123
- if (sessionId) {
124
- const session = await this.getSessionById(sessionId)
125
- if (session) {
126
- const user = await this.getUserByName(session.username)
127
- if (user) {
128
- return user
129
- }
130
- }
131
- }
132
-
133
103
  throw new UnauthenticatedError()
134
104
  }
135
105
 
@@ -144,7 +114,7 @@ export class HttpUserContext {
144
114
  serverResponse: { setHeader: (header: string, value: string) => void },
145
115
  ): Promise<User> {
146
116
  const sessionId = randomBytes(32).toString('hex')
147
- await this.getSessionStore().add({ sessionId, username: user.username })
117
+ await this.getSessionDataSet().add(this.systemInjector, { sessionId, username: user.username })
148
118
  serverResponse.setHeader('Set-Cookie', `${this.authentication.cookieName}=${sessionId}; Path=/; HttpOnly`)
149
119
  this.user = user
150
120
  return user
@@ -159,36 +129,36 @@ export class HttpUserContext {
159
129
  response.setHeader('Set-Cookie', `${this.authentication.cookieName}=; Path=/; HttpOnly`)
160
130
 
161
131
  if (sessionId) {
162
- const sessionStore = this.getSessionStore()
163
- const sessions = await sessionStore.find({ filter: { sessionId: { $eq: sessionId } } })
164
- await this.getSessionStore().remove(...sessions.map((s) => s[sessionStore.primaryKey]))
132
+ const sessionDataSet = this.getSessionDataSet()
133
+ const sessions = await sessionDataSet.find(this.systemInjector, { filter: { sessionId: { $eq: sessionId } } })
134
+ await sessionDataSet.remove(this.systemInjector, ...sessions.map((s) => s[sessionDataSet.primaryKey]))
165
135
  }
166
136
  }
167
137
 
168
138
  @Injected(HttpAuthenticationSettings)
169
139
  declare public readonly authentication: HttpAuthenticationSettings<User, DefaultSession>
170
140
 
171
- @Injected(StoreManager)
172
- declare private readonly storeManager: StoreManager
141
+ @Injected((injector: Injector) => useSystemIdentityContext({ injector, username: 'HttpUserContext' }))
142
+ declare private readonly systemInjector: Injector
173
143
 
174
144
  @Injected(PasswordAuthenticator)
175
145
  declare private readonly authenticator: PasswordAuthenticator
176
146
 
177
147
  public init() {
178
- this.getUserStore().addListener('onEntityUpdated', ({ id, change }) => {
148
+ this.getUserDataSet().addListener('onEntityUpdated', ({ id, change }) => {
179
149
  if (this.user?.username === id) {
180
150
  this.user = { ...this.user, ...change }
181
151
  }
182
152
  })
183
153
 
184
- this.getUserStore().addListener('onEntityRemoved', ({ key }) => {
154
+ this.getUserDataSet().addListener('onEntityRemoved', ({ key }) => {
185
155
  if (this.user?.username === key) {
186
156
  this.user = undefined
187
157
  }
188
158
  })
189
159
 
190
- this.getSessionStore().addListener('onEntityRemoved', () => {
191
- this.user = undefined // as user cannot be determined by the session id anymore
160
+ this.getSessionDataSet().addListener('onEntityRemoved', () => {
161
+ this.user = undefined
192
162
  })
193
163
  }
194
164
  }
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ export * from './actions/index.js'
2
2
  export * from './add-cors-header.js'
3
3
  export * from './api-manager.js'
4
4
  export * from './authenticate.js'
5
+ export * from './authentication-providers/index.js'
5
6
  export * from './authorize.js'
6
7
  export * from './endpoint-generators/index.js'
7
8
  export * from './get-schema-from-api.js'
@@ -1,6 +1,7 @@
1
1
  import { InMemoryStore, User, addStore } from '@furystack/core'
2
2
  import { getPort } from '@furystack/core/port-generator'
3
3
  import { Injector } from '@furystack/inject'
4
+ import { getRepository } from '@furystack/repository'
4
5
  import type { RestApi } from '@furystack/rest'
5
6
  import { serializeValue } from '@furystack/rest'
6
7
  import { PathHelper, usingAsync } from '@furystack/utils'
@@ -33,10 +34,10 @@ const createIntegrationApi = async () => {
33
34
  addStore(i, new InMemoryStore({ model: User, primaryKey: 'username' })).addStore(
34
35
  new InMemoryStore({ model: DefaultSession, primaryKey: 'sessionId' }),
35
36
  )
36
- useHttpAuthentication(i, {
37
- getUserStore: (sm) => sm.getStoreFor(User, 'username'),
38
- getSessionStore: (sm) => sm.getStoreFor(DefaultSession, 'sessionId'),
39
- })
37
+ const repo = getRepository(i)
38
+ repo.createDataSet(User, 'username')
39
+ repo.createDataSet(DefaultSession, 'sessionId')
40
+ useHttpAuthentication(i)
40
41
  await useRestService<IntegrationTestApi>({
41
42
  injector: i,
42
43
  root,
@@ -2,6 +2,7 @@
2
2
  import { getStoreManager, InMemoryStore, User } from '@furystack/core'
3
3
  import { getPort } from '@furystack/core/port-generator'
4
4
  import { Injector } from '@furystack/inject'
5
+ import { getRepository } from '@furystack/repository'
5
6
  import type { SwaggerDocument, WithSchemaAction } from '@furystack/rest'
6
7
  import { createClient, ResponseError } from '@furystack/rest-client-fetch'
7
8
  import { usingAsync } from '@furystack/utils'
@@ -32,6 +33,10 @@ const createValidateApi = async (options = { enableGetSchema: false }) => {
32
33
 
33
34
  getStoreManager(injector).addStore(new InMemoryStore({ model: User, primaryKey: 'username' }))
34
35
  getStoreManager(injector).addStore(new InMemoryStore({ model: DefaultSession, primaryKey: 'sessionId' }))
36
+ getStoreManager(injector).addStore(new InMemoryStore({ model: MockClass, primaryKey: 'id' }))
37
+ getRepository(injector).createDataSet(MockClass, 'id')
38
+ getRepository(injector).createDataSet(User, 'username')
39
+ getRepository(injector).createDataSet(DefaultSession, 'sessionId')
35
40
 
36
41
  const api = await useRestService<ValidationApi>({
37
42
  injector,