@owlmeans/server-auth 0.1.2 → 0.1.3

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 (54) hide show
  1. package/README.md +33 -454
  2. package/build/actions/service.d.ts +2 -2
  3. package/build/actions/service.d.ts.map +1 -1
  4. package/build/actions/service.js.map +1 -1
  5. package/build/manager/actions/manager.d.ts +4 -4
  6. package/build/manager/actions/manager.d.ts.map +1 -1
  7. package/build/manager/actions/manager.js.map +1 -1
  8. package/build/manager/exports.d.ts +4 -4
  9. package/build/manager/exports.d.ts.map +1 -1
  10. package/build/manager/exports.js +2 -2
  11. package/build/manager/exports.js.map +1 -1
  12. package/build/manager/main.js +1 -1
  13. package/build/manager/main.js.map +1 -1
  14. package/build/manager/model.js +2 -2
  15. package/build/manager/model.js.map +1 -1
  16. package/build/manager/modules.d.ts +1 -1
  17. package/build/manager/modules.d.ts.map +1 -1
  18. package/build/manager/modules.js +3 -3
  19. package/build/manager/modules.js.map +1 -1
  20. package/build/manager/plugins/re-captcha.js +3 -3
  21. package/build/manager/plugins/re-captcha.js.map +1 -1
  22. package/build/manager/plugins/types.d.ts +1 -1
  23. package/build/manager/plugins/types.d.ts.map +1 -1
  24. package/build/manager/plugins/utils.d.ts +1 -1
  25. package/build/manager/plugins/utils.d.ts.map +1 -1
  26. package/build/manager/plugins/utils.js +2 -2
  27. package/build/manager/plugins/utils.js.map +1 -1
  28. package/build/manager/types.d.ts +1 -1
  29. package/build/manager/types.d.ts.map +1 -1
  30. package/build/modules.d.ts +2 -2
  31. package/build/modules.d.ts.map +1 -1
  32. package/build/modules.js +2 -2
  33. package/build/modules.js.map +1 -1
  34. package/build/types.d.ts +1 -1
  35. package/build/types.d.ts.map +1 -1
  36. package/package.json +30 -26
  37. package/src/actions/service.ts +2 -2
  38. package/src/manager/actions/manager.ts +4 -4
  39. package/src/manager/exports.ts +4 -4
  40. package/src/manager/main.ts +1 -1
  41. package/src/manager/model.ts +2 -2
  42. package/src/manager/modules.ts +3 -3
  43. package/src/manager/plugins/re-captcha.ts +4 -4
  44. package/src/manager/plugins/types.ts +1 -1
  45. package/src/manager/plugins/utils.ts +2 -2
  46. package/src/manager/rely.ts +1 -1
  47. package/src/manager/types.ts +1 -1
  48. package/src/modules.ts +5 -5
  49. package/src/service.ts +1 -1
  50. package/src/types.ts +1 -1
  51. package/tests/context.ts +59 -0
  52. package/tests/service.spec.ts +174 -0
  53. package/tests/tsconfig.json +12 -0
  54. package/tsconfig.json +6 -5
@@ -0,0 +1,174 @@
1
+ import { describe, test, expect } from 'bun:test'
2
+ import { makeTestContext } from './context.js'
3
+ import { makeFixtureKeyPair } from '@owlmeans/test-auth'
4
+ import { EnvelopeKind, makeEnvelopeModel } from '@owlmeans/basic-envelope'
5
+ import { AuthenFailed, AuthroizationType, AuthRole } from '@owlmeans/auth'
6
+ import type { AuthCredentials, Auth } from '@owlmeans/auth'
7
+ import { DEFAULT_ALIAS } from '../src/consts.js'
8
+ import type { AuthService } from '../src/types.js'
9
+
10
+ const initContext = async () => {
11
+ const { context, authServiceKP, appKP, authServiceRecord } = makeTestContext()
12
+ context.configure()
13
+ await context.init()
14
+ return { context, authServiceKP, appKP, authServiceRecord }
15
+ }
16
+
17
+ describe('@owlmeans/server-auth — authenticate (mode 2: external auth service credentials)', () => {
18
+ test('exchanges valid signed intermediate credentials into a bearer token', async () => {
19
+ const { context, authServiceKP, authServiceRecord } = await initContext()
20
+ const authService = context.service<AuthService>(DEFAULT_ALIAS)
21
+
22
+ // Build intermediate credentials as the external auth service would produce
23
+ const credentials: AuthCredentials = {
24
+ type: 'ed25519-basic-token',
25
+ challenge: 'unique-nonce-' + Date.now(),
26
+ credential: authServiceRecord.id, // must match trustedUser.id
27
+ role: AuthRole.User,
28
+ userId: 'user-123',
29
+ scopes: ['*'],
30
+ profileId: 'profile-abc',
31
+ }
32
+
33
+ // Sign the credentials with the auth service's secret key
34
+ const signedToken = await makeEnvelopeModel<AuthCredentials>(AuthroizationType.Ed25519BasicToken)
35
+ .send(credentials, null).sign(authServiceKP, EnvelopeKind.Token)
36
+
37
+ const result = await authService.authenticate({ token: signedToken })
38
+
39
+ expect(result.token).toStartWith('ED25519-BASIC-TOKEN ')
40
+ // The token should be verifiable with the app's own key
41
+ const [, authorization] = result.token.split(' ')
42
+ expect(authorization.length).toBeGreaterThan(0)
43
+ })
44
+
45
+ test('rejects credentials signed by an unknown key', async () => {
46
+ const { context } = await initContext()
47
+ const authService = context.service<AuthService>(DEFAULT_ALIAS)
48
+
49
+ const unknownKP = makeFixtureKeyPair('unknown-signer')
50
+
51
+ const credentials: AuthCredentials = {
52
+ type: 'ed25519-basic-token',
53
+ challenge: 'nonce-' + Date.now(),
54
+ credential: 'wrong-id',
55
+ role: AuthRole.User,
56
+ userId: 'user-456',
57
+ scopes: ['*'],
58
+ }
59
+
60
+ const signedToken = await makeEnvelopeModel<AuthCredentials>(AuthroizationType.Ed25519BasicToken)
61
+ .send(credentials, null).sign(unknownKP, EnvelopeKind.Token)
62
+
63
+ await expect(authService.authenticate({ token: signedToken })).rejects.toBeInstanceOf(AuthenFailed)
64
+ })
65
+
66
+ test('rejects when credential does not match trusted user id (anti-impersonation)', async () => {
67
+ const { context, authServiceKP } = await initContext()
68
+ const authService = context.service<AuthService>(DEFAULT_ALIAS)
69
+
70
+ const credentials: AuthCredentials = {
71
+ type: 'ed25519-basic-token',
72
+ challenge: 'nonce-impersonate-' + Date.now(),
73
+ credential: 'wrong-service-user-id', // does not match authServiceRecord.id
74
+ role: AuthRole.User,
75
+ userId: 'user-789',
76
+ scopes: ['*'],
77
+ }
78
+
79
+ const signedToken = await makeEnvelopeModel<AuthCredentials>(AuthroizationType.Ed25519BasicToken)
80
+ .send(credentials, null).sign(authServiceKP, EnvelopeKind.Token)
81
+
82
+ await expect(authService.authenticate({ token: signedToken })).rejects.toBeInstanceOf(AuthenFailed)
83
+ })
84
+
85
+ test('rejects replay of already-used challenge (anti-replay cache)', async () => {
86
+ const { context, authServiceKP, authServiceRecord } = await initContext()
87
+ const authService = context.service<AuthService>(DEFAULT_ALIAS)
88
+
89
+ const challenge = 'replay-nonce-' + Date.now()
90
+ const credentials: AuthCredentials = {
91
+ type: 'ed25519-basic-token',
92
+ challenge,
93
+ credential: authServiceRecord.id,
94
+ role: AuthRole.User,
95
+ userId: 'user-replay',
96
+ scopes: ['*'],
97
+ }
98
+
99
+ const signedToken = await makeEnvelopeModel<AuthCredentials>(AuthroizationType.Ed25519BasicToken)
100
+ .send(credentials, null).sign(authServiceKP, EnvelopeKind.Token)
101
+
102
+ // First call succeeds
103
+ await authService.authenticate({ token: signedToken })
104
+
105
+ // Second call with same challenge should fail (replay)
106
+ await expect(authService.authenticate({ token: signedToken })).rejects.toBeInstanceOf(AuthenFailed)
107
+ })
108
+ })
109
+
110
+ describe('@owlmeans/server-auth — handle (mode 3: verify bearer token)', () => {
111
+ test('verifies a bearer token issued by authenticate()', async () => {
112
+ const { context, authServiceKP, authServiceRecord } = await initContext()
113
+ const authService = context.service<AuthService>(DEFAULT_ALIAS)
114
+
115
+ // First get a valid bearer via authenticate
116
+ const credentials: AuthCredentials = {
117
+ type: 'ed25519-basic-token',
118
+ challenge: 'handle-nonce-' + Date.now(),
119
+ credential: authServiceRecord.id,
120
+ role: AuthRole.User,
121
+ userId: 'user-handle',
122
+ scopes: ['*'],
123
+ }
124
+
125
+ const signedToken = await makeEnvelopeModel<AuthCredentials>(AuthroizationType.Ed25519BasicToken)
126
+ .send(credentials, null).sign(authServiceKP, EnvelopeKind.Token)
127
+
128
+ const { token: bearer } = await authService.authenticate({ token: signedToken })
129
+
130
+ // Now verify it via handle()
131
+ const auth: { value: Auth | null } = { value: null }
132
+ const req = { headers: { authorization: bearer } } as any
133
+ const res = { resolve: (v: Auth) => { auth.value = v } } as any
134
+
135
+ const handled = await authService.handle(req, res)
136
+
137
+ expect(handled).toBe(true)
138
+ expect(auth.value).not.toBeNull()
139
+ expect(auth.value!.userId).toBe('user-handle')
140
+ expect(auth.value!.scopes).toEqual(['*'])
141
+ })
142
+
143
+ test('returns false for missing authorization header', async () => {
144
+ const { context } = await initContext()
145
+ const authService = context.service<AuthService>(DEFAULT_ALIAS)
146
+
147
+ const req = { headers: {} } as any
148
+ const res = { resolve: () => {} } as any
149
+
150
+ const handled = await authService.handle(req, res)
151
+
152
+ expect(handled).toBe(false)
153
+ })
154
+ })
155
+
156
+ describe('@owlmeans/server-auth — match', () => {
157
+ test('matches Ed25519BasicToken authorization header', async () => {
158
+ const { context } = await initContext()
159
+ const authService = context.service<AuthService>(DEFAULT_ALIAS)
160
+
161
+ const req = { headers: { authorization: 'ED25519-BASIC-TOKEN abc123' } } as any
162
+ const res = {} as any
163
+ expect(await authService.match(req, res)).toBe(true)
164
+ })
165
+
166
+ test('does not match other authorization types', async () => {
167
+ const { context } = await initContext()
168
+ const authService = context.service<AuthService>(DEFAULT_ALIAS)
169
+
170
+ const req = { headers: { authorization: 'Bearer xyz' } } as any
171
+ const res = {} as any
172
+ expect(await authService.match(req, res)).toBe(false)
173
+ })
174
+ })
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": [
3
+ "@owlmeans/dep-config/tsconfig.base.json",
4
+ "@owlmeans/dep-config/tsconfig.node.json"
5
+ ],
6
+ "compilerOptions": {
7
+ "types": ["bun"],
8
+ "rootDir": "../",
9
+ "noEmit": true
10
+ },
11
+ "include": ["./**/*", "../src/**/*"]
12
+ }
package/tsconfig.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "extends": [
3
- "../tsconfig.default.json",
3
+ "@owlmeans/dep-config/tsconfig.base.json",
4
+ "@owlmeans/dep-config/tsconfig.node.json"
4
5
  ],
5
6
  "compilerOptions": {
6
- "rootDir": "./src/", /* Specify the root folder within your source files. */
7
- "outDir": "./build/", /* Specify an output folder for all emitted files. */
8
- "moduleResolution": "Bundler"
7
+ "rootDir": "./src/",
8
+ "outDir": "./build/"
9
9
  },
10
10
  "exclude": [
11
11
  "./dist/**/*",
12
12
  "./build/**/*",
13
+ "./tests/**/*",
13
14
  "./*.ts"
14
15
  ]
15
- }
16
+ }