@furystack/rest-service 11.0.7 → 12.1.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 (104) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/README.md +3 -2
  3. package/esm/actions/index.d.ts +1 -0
  4. package/esm/actions/index.d.ts.map +1 -1
  5. package/esm/actions/index.js +1 -0
  6. package/esm/actions/index.js.map +1 -1
  7. package/esm/actions/login.d.ts +7 -3
  8. package/esm/actions/login.d.ts.map +1 -1
  9. package/esm/actions/login.js +11 -5
  10. package/esm/actions/login.js.map +1 -1
  11. package/esm/actions/password-login-action.d.ts +24 -0
  12. package/esm/actions/password-login-action.d.ts.map +1 -0
  13. package/esm/actions/password-login-action.js +31 -0
  14. package/esm/actions/password-login-action.js.map +1 -0
  15. package/esm/actions/password-login-action.spec.d.ts +2 -0
  16. package/esm/actions/password-login-action.spec.d.ts.map +1 -0
  17. package/esm/actions/password-login-action.spec.js +105 -0
  18. package/esm/actions/password-login-action.spec.js.map +1 -0
  19. package/esm/authenticate.d.ts.map +1 -1
  20. package/esm/authenticate.js +4 -1
  21. package/esm/authenticate.js.map +1 -1
  22. package/esm/authenticate.spec.js +6 -4
  23. package/esm/authenticate.spec.js.map +1 -1
  24. package/esm/authentication-providers/authentication-provider.d.ts +25 -0
  25. package/esm/authentication-providers/authentication-provider.d.ts.map +1 -0
  26. package/esm/authentication-providers/authentication-provider.js +2 -0
  27. package/esm/authentication-providers/authentication-provider.js.map +1 -0
  28. package/esm/authentication-providers/basic-auth-provider.d.ts +8 -0
  29. package/esm/authentication-providers/basic-auth-provider.d.ts.map +1 -0
  30. package/esm/authentication-providers/basic-auth-provider.js +18 -0
  31. package/esm/authentication-providers/basic-auth-provider.js.map +1 -0
  32. package/esm/authentication-providers/cookie-auth-provider.d.ts +11 -0
  33. package/esm/authentication-providers/cookie-auth-provider.d.ts.map +1 -0
  34. package/esm/authentication-providers/cookie-auth-provider.js +21 -0
  35. package/esm/authentication-providers/cookie-auth-provider.js.map +1 -0
  36. package/esm/authentication-providers/helpers.d.ts +11 -0
  37. package/esm/authentication-providers/helpers.d.ts.map +1 -0
  38. package/esm/authentication-providers/helpers.js +47 -0
  39. package/esm/authentication-providers/helpers.js.map +1 -0
  40. package/esm/authentication-providers/index.d.ts +5 -0
  41. package/esm/authentication-providers/index.d.ts.map +1 -0
  42. package/esm/authentication-providers/index.js +5 -0
  43. package/esm/authentication-providers/index.js.map +1 -0
  44. package/esm/endpoint-generators/utils.d.ts.map +1 -1
  45. package/esm/endpoint-generators/utils.js +4 -1
  46. package/esm/endpoint-generators/utils.js.map +1 -1
  47. package/esm/helpers.d.ts +5 -2
  48. package/esm/helpers.d.ts.map +1 -1
  49. package/esm/helpers.js +27 -3
  50. package/esm/helpers.js.map +1 -1
  51. package/esm/helpers.spec.js +37 -0
  52. package/esm/helpers.spec.js.map +1 -1
  53. package/esm/http-authentication-settings.d.ts +11 -4
  54. package/esm/http-authentication-settings.d.ts.map +1 -1
  55. package/esm/http-authentication-settings.js +9 -2
  56. package/esm/http-authentication-settings.js.map +1 -1
  57. package/esm/http-user-context.d.ts +9 -4
  58. package/esm/http-user-context.d.ts.map +1 -1
  59. package/esm/http-user-context.js +28 -55
  60. package/esm/http-user-context.js.map +1 -1
  61. package/esm/http-user-context.spec.d.ts +3 -1
  62. package/esm/http-user-context.spec.d.ts.map +1 -1
  63. package/esm/http-user-context.spec.js +103 -45
  64. package/esm/http-user-context.spec.js.map +1 -1
  65. package/esm/index.d.ts +2 -0
  66. package/esm/index.d.ts.map +1 -1
  67. package/esm/index.js +2 -0
  68. package/esm/index.js.map +1 -1
  69. package/esm/login-response-strategy.d.ts +28 -0
  70. package/esm/login-response-strategy.d.ts.map +1 -0
  71. package/esm/login-response-strategy.js +28 -0
  72. package/esm/login-response-strategy.js.map +1 -0
  73. package/esm/login-response-strategy.spec.d.ts +2 -0
  74. package/esm/login-response-strategy.spec.d.ts.map +1 -0
  75. package/esm/login-response-strategy.spec.js +78 -0
  76. package/esm/login-response-strategy.spec.js.map +1 -0
  77. package/esm/rest-service.integration.spec.d.ts.map +1 -1
  78. package/esm/rest-service.integration.spec.js +5 -4
  79. package/esm/rest-service.integration.spec.js.map +1 -1
  80. package/esm/validate.integration.spec.js +5 -0
  81. package/esm/validate.integration.spec.js.map +1 -1
  82. package/package.json +7 -7
  83. package/src/actions/index.ts +1 -0
  84. package/src/actions/login.ts +12 -6
  85. package/src/actions/password-login-action.spec.ts +122 -0
  86. package/src/actions/password-login-action.ts +35 -0
  87. package/src/authenticate.spec.ts +6 -4
  88. package/src/authenticate.ts +4 -1
  89. package/src/authentication-providers/authentication-provider.ts +25 -0
  90. package/src/authentication-providers/basic-auth-provider.ts +21 -0
  91. package/src/authentication-providers/cookie-auth-provider.ts +26 -0
  92. package/src/authentication-providers/helpers.ts +73 -0
  93. package/src/authentication-providers/index.ts +4 -0
  94. package/src/endpoint-generators/utils.ts +4 -1
  95. package/src/helpers.spec.ts +40 -0
  96. package/src/helpers.ts +48 -3
  97. package/src/http-authentication-settings.ts +30 -21
  98. package/src/http-user-context.spec.ts +462 -394
  99. package/src/http-user-context.ts +164 -194
  100. package/src/index.ts +2 -0
  101. package/src/login-response-strategy.spec.ts +90 -0
  102. package/src/login-response-strategy.ts +48 -0
  103. package/src/rest-service.integration.spec.ts +5 -4
  104. package/src/validate.integration.spec.ts +5 -0
@@ -1,21 +1,61 @@
1
+ import { InMemoryStore, User, addStore } from '@furystack/core'
1
2
  import { getPort } from '@furystack/core/port-generator'
2
3
  import { Injector } from '@furystack/inject'
4
+ import { getRepository } from '@furystack/repository'
5
+ import { PasswordCredential, PasswordResetToken, usePasswordPolicy } from '@furystack/security'
3
6
  import { usingAsync } from '@furystack/utils'
4
7
  import { describe, expect, it } from 'vitest'
5
8
  import { ApiManager } from './api-manager.js'
6
9
  import { useHttpAuthentication, useProxy, useRestService, useStaticFiles } from './helpers.js'
7
10
  import { HttpAuthenticationSettings } from './http-authentication-settings.js'
11
+ import { DefaultSession } from './models/default-session.js'
8
12
  import { ProxyManager } from './proxy-manager.js'
9
13
  import { StaticServerManager } from './static-server-manager.js'
10
14
 
15
+ const setupAuthStores = (i: Injector) => {
16
+ addStore(i, new InMemoryStore({ model: User, primaryKey: 'username' }))
17
+ .addStore(new InMemoryStore({ model: DefaultSession, primaryKey: 'sessionId' }))
18
+ .addStore(new InMemoryStore({ model: PasswordCredential, primaryKey: 'userName' }))
19
+ .addStore(new InMemoryStore({ model: PasswordResetToken, primaryKey: 'token' }))
20
+ const repo = getRepository(i)
21
+ repo.createDataSet(User, 'username')
22
+ repo.createDataSet(DefaultSession, 'sessionId')
23
+ repo.createDataSet(PasswordCredential, 'userName')
24
+ repo.createDataSet(PasswordResetToken, 'token')
25
+ usePasswordPolicy(i)
26
+ }
27
+
11
28
  describe('Injector extensions', () => {
12
29
  describe('useHttpAuthentication', () => {
13
30
  it('Should set up HTTP Authentication', async () => {
14
31
  await usingAsync(new Injector(), async (i) => {
32
+ setupAuthStores(i)
15
33
  useHttpAuthentication(i)
16
34
  expect(i.cachedSingletons.get(HttpAuthenticationSettings)).toBeDefined()
17
35
  })
18
36
  })
37
+
38
+ it('Should register basic-auth and cookie-auth providers by default', async () => {
39
+ await usingAsync(new Injector(), async (i) => {
40
+ setupAuthStores(i)
41
+ useHttpAuthentication(i)
42
+ const settings = i.getInstance(HttpAuthenticationSettings)
43
+ const providerNames = settings.authenticationProviders.map((p) => p.name)
44
+ expect(providerNames).toContain('basic-auth')
45
+ expect(providerNames).toContain('cookie-auth')
46
+ })
47
+ })
48
+
49
+ it('Should not register basic-auth provider when disabled', async () => {
50
+ await usingAsync(new Injector(), async (i) => {
51
+ setupAuthStores(i)
52
+ useHttpAuthentication(i, { enableBasicAuth: false })
53
+ const settings = i.getInstance(HttpAuthenticationSettings)
54
+ const providerNames = settings.authenticationProviders.map((p) => p.name)
55
+ expect(providerNames).not.toContain('basic-auth')
56
+ expect(providerNames).toContain('cookie-auth')
57
+ })
58
+ })
19
59
  })
20
60
 
21
61
  describe('useRestService()', () => {
package/src/helpers.ts CHANGED
@@ -1,8 +1,16 @@
1
1
  import type { User } from '@furystack/core'
2
+ import { useSystemIdentityContext } from '@furystack/core'
2
3
  import type { Injector } from '@furystack/inject'
3
4
  import type { RestApi } from '@furystack/rest'
5
+ import type { DataSet } from '@furystack/repository'
6
+ import { PasswordAuthenticator } from '@furystack/security'
7
+
4
8
  import type { ImplementApiOptions } from './api-manager.js'
5
9
  import { ApiManager } from './api-manager.js'
10
+ import type { AuthenticationProvider } from './authentication-providers/authentication-provider.js'
11
+ import { createBasicAuthProvider } from './authentication-providers/basic-auth-provider.js'
12
+ import { createCookieAuthProvider } from './authentication-providers/cookie-auth-provider.js'
13
+ import { authenticateUserWithDataSet, findSessionById, findUserByName } from './authentication-providers/helpers.js'
6
14
  import { HttpAuthenticationSettings } from './http-authentication-settings.js'
7
15
  import type { DefaultSession } from './models/default-session.js'
8
16
  import type { ProxyOptions } from './proxy-manager.js'
@@ -19,15 +27,52 @@ export const useRestService = async <T extends RestApi>(api: ImplementApiOptions
19
27
  await api.injector.getInstance(ApiManager).addApi({ ...api })
20
28
 
21
29
  /**
22
- * Sets up the HTTP Authentication
30
+ * Sets up the HTTP Authentication with pluggable providers.
31
+ *
32
+ * Registers Basic Auth (if enabled) and Cookie Auth as built-in providers,
33
+ * then appends any custom providers passed via settings.
34
+ *
23
35
  * @param injector The Injector instance
24
36
  * @param settings Settings for HTTP Authentication
25
- * @returns void
26
37
  */
27
38
  export const useHttpAuthentication = <TUser extends User, TSession extends DefaultSession>(
28
39
  injector: Injector,
29
40
  settings?: Partial<HttpAuthenticationSettings<TUser, TSession>>,
30
- ) => injector.setExplicitInstance(Object.assign(new HttpAuthenticationSettings(), settings), HttpAuthenticationSettings)
41
+ ) => {
42
+ const mergedSettings = Object.assign(new HttpAuthenticationSettings<TUser, TSession>(), settings)
43
+ const systemInjector = useSystemIdentityContext({ injector, username: 'useHttpAuthentication' })
44
+ const passwordAuthenticator = injector.getInstance(PasswordAuthenticator)
45
+ const userDataSet = mergedSettings.getUserDataSet(systemInjector)
46
+ // Narrow from DataSet<TSession, keyof TSession> to DataSet<DefaultSession, 'sessionId'>
47
+ // because the built-in providers operate on DefaultSession with the concrete 'sessionId' key
48
+ const sessionDataSet = mergedSettings.getSessionDataSet(systemInjector) as unknown as DataSet<
49
+ DefaultSession,
50
+ 'sessionId'
51
+ >
52
+
53
+ const providers: AuthenticationProvider[] = []
54
+
55
+ if (mergedSettings.enableBasicAuth) {
56
+ providers.push(
57
+ createBasicAuthProvider((username, password) =>
58
+ authenticateUserWithDataSet(passwordAuthenticator, userDataSet, systemInjector, username, password),
59
+ ),
60
+ )
61
+ }
62
+
63
+ providers.push(
64
+ createCookieAuthProvider(
65
+ mergedSettings.cookieName,
66
+ (sessionId) => findSessionById(sessionDataSet, systemInjector, sessionId),
67
+ (username) => findUserByName(userDataSet, systemInjector, username),
68
+ ),
69
+ )
70
+
71
+ providers.push(...(settings?.authenticationProviders ?? []))
72
+ mergedSettings.authenticationProviders = providers
73
+
74
+ injector.setExplicitInstance(mergedSettings, HttpAuthenticationSettings)
75
+ }
31
76
 
32
77
  /**
33
78
  * Sets up a static file server
@@ -1,21 +1,30 @@
1
- import type { PhysicalStore, StoreManager } from '@furystack/core'
2
- import { User } from '@furystack/core'
3
- import type { Constructable } from '@furystack/inject'
4
- import { Injectable } from '@furystack/inject'
5
- import { DefaultSession } from './models/default-session.js'
6
-
7
- /**
8
- * Authentication settings object for FuryStack HTTP Api
9
- */
10
- @Injectable({ lifetime: 'singleton' })
11
- export class HttpAuthenticationSettings<TUser extends User, TSession extends DefaultSession> {
12
- public model: Constructable<TUser> = User as Constructable<TUser>
13
-
14
- public getUserStore = (sm: StoreManager) => sm.getStoreFor(User, 'username')
15
-
16
- public getSessionStore: (storeManager: StoreManager) => PhysicalStore<TSession, keyof TSession> = (sm) =>
17
- sm.getStoreFor(DefaultSession, 'sessionId') as unknown as PhysicalStore<TSession, keyof TSession>
18
-
19
- public cookieName = 'fss'
20
- public enableBasicAuth = true
21
- }
1
+ import { User } from '@furystack/core'
2
+ import type { Constructable, Injector } from '@furystack/inject'
3
+ import { Injectable } from '@furystack/inject'
4
+ import type { DataSet } from '@furystack/repository'
5
+ import { getDataSetFor } from '@furystack/repository'
6
+ import type { AuthenticationProvider } from './authentication-providers/authentication-provider.js'
7
+ import { DefaultSession } from './models/default-session.js'
8
+
9
+ /**
10
+ * Authentication settings object for FuryStack HTTP Api
11
+ */
12
+ @Injectable({ lifetime: 'singleton' })
13
+ export class HttpAuthenticationSettings<TUser extends User, TSession extends DefaultSession> {
14
+ public model: Constructable<TUser> = User as Constructable<TUser>
15
+
16
+ public getUserDataSet = (injector: Injector) => getDataSetFor(injector, User, 'username')
17
+
18
+ public getSessionDataSet: (injector: Injector) => DataSet<TSession, keyof TSession> = (injector) =>
19
+ getDataSetFor(injector, DefaultSession, 'sessionId') as unknown as DataSet<TSession, keyof TSession>
20
+
21
+ public cookieName = 'fss'
22
+ public enableBasicAuth = true
23
+
24
+ /**
25
+ * Ordered list of authentication providers. Populated by {@link useHttpAuthentication}
26
+ * and extended by `useJwtAuthentication()` or other auth plugins.
27
+ * Safe to mutate only during setup, before the first request is served.
28
+ */
29
+ public authenticationProviders: AuthenticationProvider[] = []
30
+ }