@passlock/client 0.9.22 → 0.9.24

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 (156) hide show
  1. package/README.md +10 -5
  2. package/README.template.md +130 -0
  3. package/dist/authentication/authenticate.d.ts +16 -16
  4. package/dist/authentication/authenticate.fixture.d.ts +21 -7
  5. package/dist/authentication/authenticate.fixture.js +7 -5
  6. package/dist/authentication/authenticate.fixture.js.map +1 -1
  7. package/dist/authentication/authenticate.js +19 -8
  8. package/dist/authentication/authenticate.js.map +1 -1
  9. package/dist/capabilities/capabilities.d.ts +9 -5
  10. package/dist/capabilities/capabilities.js +11 -2
  11. package/dist/capabilities/capabilities.js.map +1 -1
  12. package/dist/connection/connection.d.ts +11 -7
  13. package/dist/connection/connection.fixture.d.ts +2 -2
  14. package/dist/connection/connection.fixture.js +2 -1
  15. package/dist/connection/connection.fixture.js.map +1 -1
  16. package/dist/connection/connection.js +12 -3
  17. package/dist/connection/connection.js.map +1 -1
  18. package/dist/effect.d.ts +23 -46
  19. package/dist/effect.js +55 -51
  20. package/dist/effect.js.map +1 -1
  21. package/dist/email/email.d.ts +42 -12
  22. package/dist/email/email.fixture.d.ts +19 -5
  23. package/dist/email/email.fixture.js +5 -4
  24. package/dist/email/email.fixture.js.map +1 -1
  25. package/dist/email/email.js +44 -8
  26. package/dist/email/email.js.map +1 -1
  27. package/dist/event/event.d.ts +4 -2
  28. package/dist/event/event.js +4 -1
  29. package/dist/event/event.js.map +1 -1
  30. package/dist/index.d.ts +105 -27
  31. package/dist/index.js +101 -50
  32. package/dist/index.js.map +1 -1
  33. package/dist/logging/eventLogger.d.ts +13 -1
  34. package/dist/logging/eventLogger.js +14 -1
  35. package/dist/logging/eventLogger.js.map +1 -1
  36. package/dist/registration/register.d.ts +19 -22
  37. package/dist/registration/register.fixture.d.ts +20 -6
  38. package/dist/registration/register.fixture.js +14 -7
  39. package/dist/registration/register.fixture.js.map +1 -1
  40. package/dist/registration/register.js +18 -9
  41. package/dist/registration/register.js.map +1 -1
  42. package/dist/rpc/authentication.d.ts +1 -2
  43. package/dist/rpc/authentication.js +2 -1
  44. package/dist/rpc/authentication.js.map +1 -1
  45. package/dist/rpc/client.d.ts +5 -2
  46. package/dist/rpc/client.js +12 -3
  47. package/dist/rpc/client.js.map +1 -1
  48. package/dist/rpc/config.d.ts +0 -1
  49. package/dist/rpc/connection.d.ts +1 -2
  50. package/dist/rpc/connection.js +2 -1
  51. package/dist/rpc/connection.js.map +1 -1
  52. package/dist/rpc/registration.d.ts +1 -2
  53. package/dist/rpc/registration.js +2 -1
  54. package/dist/rpc/registration.js.map +1 -1
  55. package/dist/rpc/social.d.ts +1 -2
  56. package/dist/rpc/social.js +2 -1
  57. package/dist/rpc/social.js.map +1 -1
  58. package/dist/rpc/user.d.ts +1 -2
  59. package/dist/rpc/user.js +2 -1
  60. package/dist/rpc/user.js.map +1 -1
  61. package/dist/social/social.d.ts +17 -24
  62. package/dist/social/social.fixture.d.ts +22 -10
  63. package/dist/social/social.fixture.js +8 -14
  64. package/dist/social/social.fixture.js.map +1 -1
  65. package/dist/social/social.js +15 -11
  66. package/dist/social/social.js.map +1 -1
  67. package/dist/storage/storage.d.ts +41 -13
  68. package/dist/storage/storage.fixture.d.ts +2 -2
  69. package/dist/storage/storage.fixture.js +2 -2
  70. package/dist/storage/storage.fixture.js.map +1 -1
  71. package/dist/storage/storage.js +52 -15
  72. package/dist/storage/storage.js.map +1 -1
  73. package/dist/test/fixtures.d.ts +2 -3
  74. package/dist/test/fixtures.js +20 -5
  75. package/dist/test/fixtures.js.map +1 -1
  76. package/dist/user/user.d.ts +9 -6
  77. package/dist/user/user.fixture.d.ts +2 -2
  78. package/dist/user/user.fixture.js +9 -5
  79. package/dist/user/user.fixture.js.map +1 -1
  80. package/dist/user/user.js +12 -3
  81. package/dist/user/user.js.map +1 -1
  82. package/dist/version.d.ts +1 -2
  83. package/dist/version.js +1 -1
  84. package/dist/version.js.map +1 -1
  85. package/package.json +38 -30
  86. package/src/authentication/authenticate.fixture.ts +10 -7
  87. package/src/authentication/authenticate.test.ts +61 -18
  88. package/src/authentication/authenticate.ts +37 -33
  89. package/src/capabilities/capabilities.ts +11 -9
  90. package/src/connection/connection.fixture.ts +4 -1
  91. package/src/connection/connection.test.ts +4 -3
  92. package/src/connection/connection.ts +10 -8
  93. package/src/effect.ts +129 -134
  94. package/src/email/email.fixture.ts +7 -4
  95. package/src/email/email.test.ts +6 -5
  96. package/src/email/email.ts +27 -17
  97. package/src/event/event.node.test.ts +1 -0
  98. package/src/event/event.test.ts +1 -0
  99. package/src/event/event.ts +2 -1
  100. package/src/index.ts +235 -173
  101. package/src/logging/eventLogger.test.ts +2 -1
  102. package/src/logging/eventLogger.ts +3 -3
  103. package/src/registration/register.fixture.ts +16 -8
  104. package/src/registration/register.test.ts +16 -10
  105. package/src/registration/register.ts +37 -35
  106. package/src/rpc/authentication.ts +43 -0
  107. package/src/rpc/client.ts +174 -0
  108. package/src/rpc/config.ts +18 -0
  109. package/src/rpc/connection.ts +30 -0
  110. package/src/rpc/registration.ts +41 -0
  111. package/src/rpc/social.ts +45 -0
  112. package/src/rpc/user.ts +57 -0
  113. package/src/social/social.fixture.ts +12 -18
  114. package/src/social/social.test.ts +16 -30
  115. package/src/social/social.ts +22 -47
  116. package/src/storage/storage.fixture.ts +4 -4
  117. package/src/storage/storage.test.ts +29 -19
  118. package/src/storage/storage.ts +37 -36
  119. package/src/test/fixtures.ts +23 -6
  120. package/src/user/user.fixture.ts +19 -7
  121. package/src/user/user.test.ts +3 -5
  122. package/src/user/user.ts +16 -10
  123. package/src/version.ts +1 -0
  124. package/dist/authentication/authenticate.d.ts.map +0 -1
  125. package/dist/authentication/authenticate.fixture.d.ts.map +0 -1
  126. package/dist/capabilities/capabilities.d.ts.map +0 -1
  127. package/dist/config.d.ts +0 -18
  128. package/dist/config.d.ts.map +0 -1
  129. package/dist/config.js +0 -20
  130. package/dist/config.js.map +0 -1
  131. package/dist/connection/connection.d.ts.map +0 -1
  132. package/dist/connection/connection.fixture.d.ts.map +0 -1
  133. package/dist/effect.d.ts.map +0 -1
  134. package/dist/email/email.d.ts.map +0 -1
  135. package/dist/email/email.fixture.d.ts.map +0 -1
  136. package/dist/event/event.d.ts.map +0 -1
  137. package/dist/index.d.ts.map +0 -1
  138. package/dist/logging/eventLogger.d.ts.map +0 -1
  139. package/dist/registration/register.d.ts.map +0 -1
  140. package/dist/registration/register.fixture.d.ts.map +0 -1
  141. package/dist/rpc/authentication.d.ts.map +0 -1
  142. package/dist/rpc/client.d.ts.map +0 -1
  143. package/dist/rpc/config.d.ts.map +0 -1
  144. package/dist/rpc/connection.d.ts.map +0 -1
  145. package/dist/rpc/registration.d.ts.map +0 -1
  146. package/dist/rpc/social.d.ts.map +0 -1
  147. package/dist/rpc/user.d.ts.map +0 -1
  148. package/dist/social/social.d.ts.map +0 -1
  149. package/dist/social/social.fixture.d.ts.map +0 -1
  150. package/dist/storage/storage.d.ts.map +0 -1
  151. package/dist/storage/storage.fixture.d.ts.map +0 -1
  152. package/dist/test/fixtures.d.ts.map +0 -1
  153. package/dist/user/user.d.ts.map +0 -1
  154. package/dist/user/user.fixture.d.ts.map +0 -1
  155. package/dist/version.d.ts.map +0 -1
  156. package/src/config.ts +0 -42
@@ -1,9 +1,11 @@
1
- import { Duplicate, NotFound } from '@passlock/shared/dist/error/error.js'
2
- import { SocialClient } from '@passlock/shared/dist/rpc/social.js'
3
1
  import { Effect as E, Layer as L, Layer, LogLevel, Logger, pipe } from 'effect'
4
2
  import { describe, expect, test } from 'vitest'
5
3
  import { mock } from 'vitest-mock-extended'
4
+
5
+ import { Duplicate, NotFound } from '@passlock/shared/dist/error/error.js'
6
+
6
7
  import * as Fixture from './social.fixture.js'
8
+ import { SocialClient } from '../rpc/social.js'
7
9
  import { SocialService, SocialServiceLive } from './social.js'
8
10
 
9
11
  describe('registerOidc should', () => {
@@ -25,10 +27,7 @@ describe('registerOidc should', () => {
25
27
  }),
26
28
  )
27
29
 
28
- const service = pipe(
29
- SocialServiceLive,
30
- L.provide(rpcClientTest),
31
- )
30
+ const service = pipe(SocialServiceLive, L.provide(rpcClientTest))
32
31
 
33
32
  const layers = Layer.merge(service, rpcClientTest)
34
33
  const effect = pipe(E.provide(assertions, layers), Logger.withMinimumLogLevel(LogLevel.None))
@@ -42,7 +41,7 @@ describe('registerOidc should', () => {
42
41
  yield* _(service.registerOidc(Fixture.registerOidcReq))
43
42
 
44
43
  const rpcClient = yield* _(SocialClient)
45
- expect(rpcClient.registerOidc).toHaveBeenCalledWith(Fixture.rpcRegisterReq)
44
+ expect(rpcClient.registerOidc).toHaveBeenCalledWith(Fixture.registerOidcReq)
46
45
  })
47
46
 
48
47
  const rpcClientTest = L.effect(
@@ -56,10 +55,7 @@ describe('registerOidc should', () => {
56
55
  }),
57
56
  )
58
57
 
59
- const service = pipe(
60
- SocialServiceLive,
61
- L.provide(rpcClientTest),
62
- )
58
+ const service = pipe(SocialServiceLive, L.provide(rpcClientTest))
63
59
 
64
60
  const layers = Layer.merge(service, rpcClientTest)
65
61
  const effect = pipe(E.provide(assertions, layers), Logger.withMinimumLogLevel(LogLevel.None))
@@ -81,16 +77,13 @@ describe('registerOidc should', () => {
81
77
  E.sync(() => {
82
78
  const rpcMock = mock<SocialClient['Type']>()
83
79
 
84
- rpcMock.registerOidc.mockReturnValue(E.fail(new Duplicate({ message: "Duplicate user" })))
80
+ rpcMock.registerOidc.mockReturnValue(E.fail(new Duplicate({ message: 'Duplicate user' })))
85
81
 
86
82
  return rpcMock
87
83
  }),
88
84
  )
89
85
 
90
- const service = pipe(
91
- SocialServiceLive,
92
- L.provide(rpcClientTest),
93
- )
86
+ const service = pipe(SocialServiceLive, L.provide(rpcClientTest))
94
87
 
95
88
  const layers = Layer.merge(service, rpcClientTest)
96
89
  const effect = pipe(E.provide(assertions, layers), Logger.withMinimumLogLevel(LogLevel.None))
@@ -118,10 +111,7 @@ describe('authenticateIodc should', () => {
118
111
  }),
119
112
  )
120
113
 
121
- const service = pipe(
122
- SocialServiceLive,
123
- L.provide(rpcClientTest),
124
- )
114
+ const service = pipe(SocialServiceLive, L.provide(rpcClientTest))
125
115
 
126
116
  const layers = Layer.merge(service, rpcClientTest)
127
117
  const effect = pipe(E.provide(assertions, layers), Logger.withMinimumLogLevel(LogLevel.None))
@@ -135,7 +125,7 @@ describe('authenticateIodc should', () => {
135
125
  yield* _(service.authenticateOidc(Fixture.authOidcReq))
136
126
 
137
127
  const rpcClient = yield* _(SocialClient)
138
- expect(rpcClient.authenticateOidc).toHaveBeenCalledWith(Fixture.rpcAuthenticateReq)
128
+ expect(rpcClient.authenticateOidc).toHaveBeenCalledWith(Fixture.authOidcReq)
139
129
  })
140
130
 
141
131
  const rpcClientTest = L.effect(
@@ -149,10 +139,7 @@ describe('authenticateIodc should', () => {
149
139
  }),
150
140
  )
151
141
 
152
- const service = pipe(
153
- SocialServiceLive,
154
- L.provide(rpcClientTest),
155
- )
142
+ const service = pipe(SocialServiceLive, L.provide(rpcClientTest))
156
143
 
157
144
  const layers = Layer.merge(service, rpcClientTest)
158
145
  const effect = pipe(E.provide(assertions, layers), Logger.withMinimumLogLevel(LogLevel.None))
@@ -174,16 +161,15 @@ describe('authenticateIodc should', () => {
174
161
  E.sync(() => {
175
162
  const rpcMock = mock<SocialClient['Type']>()
176
163
 
177
- rpcMock.authenticateOidc.mockReturnValue(E.fail(new NotFound({ message: "User not found" })))
164
+ rpcMock.authenticateOidc.mockReturnValue(
165
+ E.fail(new NotFound({ message: 'User not found' })),
166
+ )
178
167
 
179
168
  return rpcMock
180
169
  }),
181
170
  )
182
171
 
183
- const service = pipe(
184
- SocialServiceLive,
185
- L.provide(rpcClientTest),
186
- )
172
+ const service = pipe(SocialServiceLive, L.provide(rpcClientTest))
187
173
 
188
174
  const layers = Layer.merge(service, rpcClientTest)
189
175
  const effect = pipe(E.provide(assertions, layers), Logger.withMinimumLogLevel(LogLevel.None))
@@ -1,51 +1,37 @@
1
1
  /**
2
2
  * Passkey authentication effects
3
3
  */
4
- import {
5
- type BadRequest,
6
- type NotSupported
7
- } from '@passlock/shared/dist/error/error.js'
8
- import * as Shared from '@passlock/shared/dist/rpc/social.js'
9
- import { SocialClient } from '@passlock/shared/dist/rpc/social.js'
10
- import type {
11
- Principal
12
- } from '@passlock/shared/dist/schema/principal.js'
13
4
  import { Context, Effect as E, Layer, flow } from 'effect'
14
5
 
6
+ import * as RPC from '@passlock/shared/dist/rpc/social.js'
7
+ import { type BadRequest, type NotSupported } from '@passlock/shared/dist/error/error.js'
8
+ import type { Principal } from '@passlock/shared/dist/schema/principal.js'
9
+
10
+ import { SocialClient } from '../rpc/social.js'
11
+
15
12
  /* Requests */
16
13
 
17
14
  export type Provider = 'apple' | 'google'
18
15
 
19
- export type RegisterOidcReq = {
20
- provider: Provider
21
- idToken: string
22
- nonce: string
23
- givenName?: string
24
- familyName?: string
25
- }
16
+ export type RegisterOidcReq = RPC.RegisterOidcReq
26
17
 
27
- export type AuthenticateOidcReq = {
28
- provider: Provider
29
- idToken: string
30
- nonce: string
31
- }
18
+ export type AuthenticateOidcReq = RPC.AuthOidcReq
32
19
 
33
20
  /* Errors */
34
21
 
35
- export type RegistrationErrors = NotSupported | BadRequest | Shared.RegisterOidcErrors
22
+ export type RegistrationErrors = NotSupported | BadRequest | RPC.RegisterOidcErrors
36
23
 
37
- export type AuthenticationErrors = NotSupported | BadRequest | Shared.AuthOidcErrors
24
+ export type AuthenticationErrors = NotSupported | BadRequest | RPC.AuthOidcErrors
38
25
 
39
26
  /* Service */
40
27
 
41
- export type SocialService = {
42
- registerOidc: (data: RegisterOidcReq) => E.Effect<Principal, RegistrationErrors>
43
- authenticateOidc: (data: AuthenticateOidcReq) => E.Effect<Principal, AuthenticationErrors>
44
- }
45
-
46
- export const SocialService = Context.GenericTag<SocialService>(
47
- '@services/SocialService',
48
- )
28
+ export class SocialService extends Context.Tag('@services/SocialService')<
29
+ SocialService,
30
+ {
31
+ registerOidc: (req: RegisterOidcReq) => E.Effect<Principal, RegistrationErrors>
32
+ authenticateOidc: (req: AuthenticateOidcReq) => E.Effect<Principal, AuthenticationErrors>
33
+ }
34
+ >() {}
49
35
 
50
36
  /* Effects */
51
37
 
@@ -58,16 +44,8 @@ export const registerOidc = (
58
44
  yield* _(E.logInfo('Registering social account'))
59
45
 
60
46
  const rpcClient = yield* _(SocialClient)
61
-
62
- const rpcRequest = new Shared.RegisterOidcReq({
63
- ...request,
64
- ...(request.givenName ? { givenName: request.givenName } : {}),
65
- ...(request.familyName ? { familyName: request.familyName } : {})
66
- })
67
-
68
- const { principal } = yield* _(
69
- rpcClient.registerOidc(rpcRequest)
70
- )
47
+ const rpcRequest = new RPC.RegisterOidcReq(request)
48
+ const { principal } = yield* _(rpcClient.registerOidc(rpcRequest))
71
49
 
72
50
  return principal
73
51
  })
@@ -80,11 +58,8 @@ export const authenticateOidc = (
80
58
  yield* _(E.logInfo('Authenticating with social account'))
81
59
 
82
60
  const rpcClient = yield* _(SocialClient)
83
- const rpcRequest = new Shared.AuthOidcReq(request)
84
-
85
- const { principal } = yield* _(
86
- rpcClient.authenticateOidc(rpcRequest)
87
- )
61
+ const rpcRequest = new RPC.AuthOidcReq(request)
62
+ const { principal } = yield* _(rpcClient.authenticateOidc(rpcRequest))
88
63
 
89
64
  return principal
90
65
  })
@@ -100,7 +75,7 @@ export const SocialServiceLive = Layer.effect(
100
75
 
101
76
  return SocialService.of({
102
77
  registerOidc: flow(registerOidc, E.provide(context)),
103
- authenticateOidc: flow(authenticateOidc, E.provide(context))
78
+ authenticateOidc: flow(authenticateOidc, E.provide(context)),
104
79
  })
105
80
  }),
106
81
  )
@@ -1,15 +1,15 @@
1
1
  import { Effect as E, Layer, pipe } from 'effect'
2
2
  import { mock } from 'vitest-mock-extended'
3
- import { Storage, StorageServiceLive } from './storage.js'
3
+
4
+ import { BrowserStorage, StorageServiceLive } from './storage.js'
4
5
 
5
6
  const storageTest = Layer.effect(
6
- Storage,
7
+ BrowserStorage,
7
8
  E.sync(() => mock<Storage>()),
8
9
  )
9
10
 
10
- export const testLayers = (storage: Layer.Layer<Storage> = storageTest) => {
11
+ export const testLayers = (storage: Layer.Layer<BrowserStorage> = storageTest) => {
11
12
  const storageService = pipe(StorageServiceLive, Layer.provide(storage))
12
-
13
13
  return Layer.merge(storage, storageService)
14
14
  }
15
15
 
@@ -1,8 +1,15 @@
1
1
  import { Effect as E, Layer, LogLevel, Logger, identity, pipe } from 'effect'
2
2
  import { describe, expect, test } from 'vitest'
3
3
  import { mock } from 'vitest-mock-extended'
4
+
4
5
  import { principal, testLayers } from './storage.fixture.js'
5
- import { Storage, StorageService, clearExpiredToken, clearToken, getToken } from './storage.js'
6
+ import {
7
+ BrowserStorage,
8
+ StorageService,
9
+ clearExpiredToken,
10
+ clearToken,
11
+ getToken,
12
+ } from './storage.js'
6
13
 
7
14
  // eslint chokes on expect(storage.setItem) etc
8
15
  /* eslint @typescript-eslint/unbound-method: 0 */
@@ -13,7 +20,7 @@ describe('storeToken should', () => {
13
20
  const service = yield* _(StorageService)
14
21
  yield* _(service.storeToken(principal))
15
22
 
16
- const storage = yield* _(Storage)
23
+ const storage = yield* _(BrowserStorage)
17
24
  expect(storage.setItem).toHaveBeenCalled()
18
25
  })
19
26
 
@@ -30,7 +37,7 @@ describe('storeToken should', () => {
30
37
  const service = yield* _(StorageService)
31
38
  yield* _(service.storeToken(principal))
32
39
 
33
- const storage = yield* _(Storage)
40
+ const storage = yield* _(BrowserStorage)
34
41
  expect(storage.setItem).toHaveBeenCalledWith('passlock:passkey:token', expect.any(String))
35
42
  })
36
43
 
@@ -47,10 +54,13 @@ describe('storeToken should', () => {
47
54
  const service = yield* _(StorageService)
48
55
  yield* _(service.storeToken(principal))
49
56
 
50
- const storage = yield* _(Storage)
51
- const token = principal.token
52
- const expiry = principal.expireAt.getTime()
53
- expect(storage.setItem).toHaveBeenCalledWith('passlock:passkey:token', `${token}:${expiry}`)
57
+ const storage = yield* _(BrowserStorage)
58
+ const token = principal.jti
59
+ const expiry = principal.exp.getTime()
60
+ expect(storage.setItem).toHaveBeenCalledWith(
61
+ 'passlock:passkey:token',
62
+ `${token}:${expiry.toFixed(0)}`,
63
+ )
54
64
  })
55
65
 
56
66
  const effect = pipe(
@@ -68,17 +78,17 @@ describe('getToken should', () => {
68
78
  const service = yield* _(StorageService)
69
79
  yield* _(service.getToken('passkey'))
70
80
 
71
- const storage = yield* _(Storage)
81
+ const storage = yield* _(BrowserStorage)
72
82
  expect(storage.getItem).toHaveBeenCalled()
73
83
  expect(storage.getItem).toHaveBeenCalledWith('passlock:passkey:token')
74
84
  })
75
85
 
76
86
  const storageTest = Layer.effect(
77
- Storage,
87
+ BrowserStorage,
78
88
  E.sync(() => {
79
89
  const mockStorage = mock<Storage>()
80
90
  const expiry = Date.now() + 1000
81
- mockStorage.getItem.mockReturnValue(`token:${expiry}`)
91
+ mockStorage.getItem.mockReturnValue(`token:${expiry.toFixed(0)}`)
82
92
  return mockStorage
83
93
  }),
84
94
  )
@@ -106,11 +116,11 @@ describe('getToken should', () => {
106
116
  )
107
117
 
108
118
  const storageTest = Layer.effect(
109
- Storage,
119
+ BrowserStorage,
110
120
  E.sync(() => {
111
121
  const mockStorage = mock<Storage>()
112
122
  const expiry = Date.now() - 1000
113
- mockStorage.getItem.mockReturnValue(`token:${expiry}`)
123
+ mockStorage.getItem.mockReturnValue(`token:${expiry.toFixed(0)}`)
114
124
  return mockStorage
115
125
  }),
116
126
  )
@@ -127,7 +137,7 @@ describe('getToken should', () => {
127
137
  describe('clearToken should', () => {
128
138
  test('clear the token in local storage', () => {
129
139
  const assertions = E.gen(function* (_) {
130
- const storage = yield* _(Storage)
140
+ const storage = yield* _(BrowserStorage)
131
141
  yield* _(clearToken('passkey'))
132
142
  expect(storage.removeItem).toHaveBeenCalledWith('passlock:passkey:token')
133
143
  })
@@ -144,18 +154,18 @@ describe('clearToken should', () => {
144
154
  describe('clearExpiredToken should', () => {
145
155
  test('clear an expired token from local storage', () => {
146
156
  const assertions = E.gen(function* (_) {
147
- const storage = yield* _(Storage)
157
+ const storage = yield* _(BrowserStorage)
148
158
  yield* _(clearExpiredToken('passkey'))
149
159
  expect(storage.getItem).toHaveBeenCalledWith('passlock:passkey:token')
150
160
  expect(storage.removeItem).toHaveBeenCalledWith('passlock:passkey:token')
151
161
  })
152
162
 
153
163
  const storageTest = Layer.effect(
154
- Storage,
164
+ BrowserStorage,
155
165
  E.sync(() => {
156
166
  const mockStorage = mock<Storage>()
157
167
  const expiry = Date.now() - 1000
158
- mockStorage.getItem.mockReturnValue(`token:${expiry}`)
168
+ mockStorage.getItem.mockReturnValue(`token:${expiry.toFixed(0)}`)
159
169
  return mockStorage
160
170
  }),
161
171
  )
@@ -170,18 +180,18 @@ describe('clearExpiredToken should', () => {
170
180
 
171
181
  test('leave a live token in local storage', () => {
172
182
  const assertions = E.gen(function* (_) {
173
- const storage = yield* _(Storage)
183
+ const storage = yield* _(BrowserStorage)
174
184
  yield* _(clearExpiredToken('passkey'))
175
185
  expect(storage.getItem).toHaveBeenCalledWith('passlock:passkey:token')
176
186
  expect(storage.removeItem).not.toHaveBeenCalled()
177
187
  })
178
188
 
179
189
  const storageTest = Layer.effect(
180
- Storage,
190
+ BrowserStorage,
181
191
  E.sync(() => {
182
192
  const mockStorage = mock<Storage>()
183
193
  const expiry = Date.now() + 1000
184
- mockStorage.getItem.mockReturnValue(`token:${expiry}`)
194
+ mockStorage.getItem.mockReturnValue(`token:${expiry.toFixed(0)}`)
185
195
  return mockStorage
186
196
  }),
187
197
  )
@@ -2,10 +2,11 @@
2
2
  * Wrapper around local storage that allows us to store
3
3
  * authentication tokens in local storage for a short period.
4
4
  */
5
- import type { Principal } from '@passlock/shared/dist/schema/principal.js'
6
5
  import { Context, Effect as E, Layer, Option as O, flow, pipe } from 'effect'
7
6
  import type { NoSuchElementException } from 'effect/Cause'
8
7
 
8
+ import type { Principal } from '@passlock/shared/dist/schema/principal.js'
9
+
9
10
  /* Requests */
10
11
 
11
12
  export type AuthType = 'email' | 'passkey' | 'apple' | 'google'
@@ -13,33 +14,31 @@ export type AuthType = 'email' | 'passkey' | 'apple' | 'google'
13
14
  export type StoredToken = {
14
15
  token: string
15
16
  authType: AuthType
16
- expireAt: number
17
+ expiry: number
17
18
  }
18
19
 
19
20
  /* Service */
20
21
 
21
- export type StorageService = {
22
- storeToken: (principal: Principal) => E.Effect<void>
23
- getToken: (authType: AuthType) => E.Effect<StoredToken, NoSuchElementException>
24
- clearToken: (authType: AuthType) => E.Effect<void>
25
- clearExpiredToken: (authType: AuthType) => E.Effect<void>
26
- clearExpiredTokens: E.Effect<void>
27
- }
28
-
29
- /* Utilities */
30
-
31
- export const StorageService = Context.GenericTag<StorageService>('@services/StorageService')
22
+ export class StorageService extends Context.Tag('@services/StorageService')<
23
+ StorageService,
24
+ {
25
+ storeToken: (principal: Principal) => E.Effect<void>
26
+ getToken: (authType: AuthType) => E.Effect<StoredToken, NoSuchElementException>
27
+ clearToken: (authType: AuthType) => E.Effect<void>
28
+ clearExpiredToken: (authType: AuthType) => E.Effect<void>
29
+ clearExpiredTokens: E.Effect<void>
30
+ }
31
+ >() {}
32
32
 
33
- // inject window.localStorage to make testing easier
34
- export const Storage = Context.GenericTag<Storage>('@services/Storage')
33
+ export class BrowserStorage extends Context.Tag('@services/Storage')<BrowserStorage, Storage>() {}
35
34
 
36
35
  export const buildKey = (authType: AuthType) => `passlock:${authType}:token`
37
36
 
38
37
  // principal => token:expireAt
39
38
  export const compressToken = (principal: Principal): string => {
40
- const expireAt = principal.expireAt.getTime()
41
- const token = principal.token
42
- return `${token}:${expireAt}`
39
+ const expireAt = principal.exp.getTime()
40
+ const token = principal.jti
41
+ return `${token}:${expireAt.toFixed(0)}`
43
42
  }
44
43
 
45
44
  // token:expireAt => { authType, token, expireAt }
@@ -50,10 +49,12 @@ export const expandToken =
50
49
  if (tokens.length !== 2) return O.none()
51
50
 
52
51
  const [token, expireAtString] = tokens
52
+ if (token === undefined || expireAtString === undefined) return O.none()
53
+
53
54
  const parse = O.liftThrowable(Number.parseInt)
54
55
  const expireAt = parse(expireAtString)
55
56
 
56
- return O.map(expireAt, expireAt => ({ authType, token, expireAt }))
57
+ return O.map(expireAt, expiry => ({ authType, token, expiry }))
57
58
  }
58
59
 
59
60
  /* Effects */
@@ -63,13 +64,13 @@ export const expandToken =
63
64
  * @param principal
64
65
  * @returns
65
66
  */
66
- export const storeToken = (principal: Principal): E.Effect<void, never, Storage> => {
67
+ export const storeToken = (principal: Principal): E.Effect<void, never, BrowserStorage> => {
67
68
  return E.gen(function* (_) {
68
- const localStorage = yield* _(Storage)
69
+ const localStorage = yield* _(BrowserStorage)
69
70
 
70
71
  const storeEffect = E.try(() => {
71
72
  const compressed = compressToken(principal)
72
- const key = buildKey(principal.authStatement.authType)
73
+ const key = buildKey(principal.authType)
73
74
  localStorage.setItem(key, compressed)
74
75
  }).pipe(E.orElse(() => E.void)) // We dont care if it fails
75
76
 
@@ -79,20 +80,20 @@ export const storeToken = (principal: Principal): E.Effect<void, never, Storage>
79
80
 
80
81
  /**
81
82
  * Get stored token from local storage
82
- * @param authType
83
+ * @param authenticator
83
84
  * @returns
84
85
  */
85
86
  export const getToken = (
86
- authType: AuthType,
87
- ): E.Effect<StoredToken, NoSuchElementException, Storage> => {
87
+ authenticator: AuthType,
88
+ ): E.Effect<StoredToken, NoSuchElementException, BrowserStorage> => {
88
89
  return E.gen(function* (_) {
89
- const localStorage = yield* _(Storage)
90
+ const localStorage = yield* _(BrowserStorage)
90
91
 
91
92
  const getEffect = pipe(
92
- O.some(buildKey(authType)),
93
+ O.some(buildKey(authenticator)),
93
94
  O.flatMap(key => pipe(localStorage.getItem(key), O.fromNullable)),
94
- O.flatMap(expandToken(authType)),
95
- O.filter(({ expireAt: expireAt }) => expireAt > Date.now()),
95
+ O.flatMap(expandToken(authenticator)),
96
+ O.filter(({ expiry }) => expiry > Date.now()),
96
97
  )
97
98
 
98
99
  return yield* _(getEffect)
@@ -104,9 +105,9 @@ export const getToken = (
104
105
  * @param authType
105
106
  * @returns
106
107
  */
107
- export const clearToken = (authType: AuthType): E.Effect<void, never, Storage> => {
108
+ export const clearToken = (authType: AuthType): E.Effect<void, never, BrowserStorage> => {
108
109
  return E.gen(function* (_) {
109
- const localStorage = yield* _(Storage)
110
+ const localStorage = yield* _(BrowserStorage)
110
111
  localStorage.removeItem(buildKey(authType))
111
112
  })
112
113
  }
@@ -117,15 +118,15 @@ export const clearToken = (authType: AuthType): E.Effect<void, never, Storage> =
117
118
  * @param defer
118
119
  * @returns
119
120
  */
120
- export const clearExpiredToken = (authType: AuthType): E.Effect<void, never, Storage> => {
121
+ export const clearExpiredToken = (authType: AuthType): E.Effect<void, never, BrowserStorage> => {
121
122
  const key = buildKey(authType)
122
123
 
123
124
  const effect = E.gen(function* (_) {
124
- const storage = yield* _(Storage)
125
+ const storage = yield* _(BrowserStorage)
125
126
  const item = yield* _(O.fromNullable(storage.getItem(key)))
126
127
  const token = yield* _(expandToken(authType)(item))
127
128
 
128
- if (token.expireAt < Date.now()) {
129
+ if (token.expiry < Date.now()) {
129
130
  storage.removeItem(key)
130
131
  }
131
132
  })
@@ -140,7 +141,7 @@ export const clearExpiredToken = (authType: AuthType): E.Effect<void, never, Sto
140
141
  )
141
142
  }
142
143
 
143
- export const clearExpiredTokens: E.Effect<void, never, Storage> = E.all([
144
+ export const clearExpiredTokens: E.Effect<void, never, BrowserStorage> = E.all([
144
145
  clearExpiredToken('passkey'),
145
146
  clearExpiredToken('email'),
146
147
  clearExpiredToken('google'),
@@ -153,7 +154,7 @@ export const clearExpiredTokens: E.Effect<void, never, Storage> = E.all([
153
154
  export const StorageServiceLive = Layer.effect(
154
155
  StorageService,
155
156
  E.gen(function* (_) {
156
- const context = yield* _(E.context<Storage>())
157
+ const context = yield* _(E.context<BrowserStorage>())
157
158
 
158
159
  return {
159
160
  storeToken: flow(storeToken, E.provide(context)),
@@ -1,6 +1,8 @@
1
+ import { Effect as E, Layer as L } from 'effect'
2
+
1
3
  import { BadRequest } from '@passlock/shared/dist/error/error.js'
2
4
  import type { Principal } from '@passlock/shared/dist/schema/principal.js'
3
- import { Effect as E, Layer as L } from 'effect'
5
+
4
6
  import { Capabilities } from '../capabilities/capabilities.js'
5
7
  import { StorageService, type StoredToken } from '../storage/storage.js'
6
8
 
@@ -8,15 +10,30 @@ export const session = 'session'
8
10
  export const token = 'token'
9
11
  export const code = 'code'
10
12
  export const authType = 'passkey'
11
- export const expireAt = Date.now() + 10000
13
+ export const expiry = Date.now() + 10000
12
14
 
13
15
  export const principal: Principal = {
16
+ jti: 'token',
14
17
  token: 'token',
18
+ sub: 'user-1',
19
+ iss: 'idp.passlock.dev',
20
+ aud: 'tenancy_id',
21
+ iat: new Date(),
22
+ nbf: new Date(),
23
+ exp: new Date(Date.now() + 5 * 60 * 1000),
24
+ email: 'john.doe@gmail.com',
25
+ givenName: 'john',
26
+ familyName: 'doe',
27
+ emailVerified: false,
28
+ authType: 'passkey',
29
+ authId: 'auth-1',
30
+ userVerified: true,
31
+ // legacy
15
32
  user: {
16
- id: '1',
17
- email: 'john.doe@gmail.com',
33
+ id: 'user-1',
18
34
  givenName: 'john',
19
35
  familyName: 'doe',
36
+ email: 'john.doe@gmail.com',
20
37
  emailVerified: false,
21
38
  },
22
39
  authStatement: {
@@ -37,7 +54,7 @@ export const capabilitiesTest = L.succeed(
37
54
  }),
38
55
  )
39
56
 
40
- export const storedToken: StoredToken = { token, authType, expireAt }
57
+ export const storedToken: StoredToken = { token, authType, expiry }
41
58
 
42
59
  export const storageServiceTest = L.succeed(
43
60
  StorageService,
@@ -50,4 +67,4 @@ export const storageServiceTest = L.succeed(
50
67
  }),
51
68
  )
52
69
 
53
- export const notImplemented = new BadRequest({ message: 'Not implemented' })
70
+ export const notImplemented = new BadRequest({ message: 'Not implemented' })
@@ -1,21 +1,33 @@
1
- import { IsExistingUserReq, IsExistingUserRes, ResendEmailReq, ResendEmailRes, UserClient, VerifyEmailRes } from '@passlock/shared/dist/rpc/user.js'
2
- import { Effect as E, Layer as L } from 'effect'
1
+ import { Effect as E, Layer as L, Option as O } from 'effect'
2
+
3
+ import {
4
+ IsExistingUserReq,
5
+ IsExistingUserRes,
6
+ ResendEmailReq,
7
+ ResendEmailRes,
8
+ VerifyEmailRes,
9
+ } from '@passlock/shared/dist/rpc/user.js'
10
+
3
11
  import * as Fixtures from '../test/fixtures.js'
12
+ import { UserClient } from '../rpc/user.js'
4
13
  import type { ResendEmail } from './user.js'
5
14
 
6
15
  export const email = 'jdoe@gmail.com'
7
16
  export const isRegisteredReq = new IsExistingUserReq({ email })
8
- export const isRegisteredRes = new IsExistingUserRes({ existingUser: false })
17
+ export const isRegisteredRes = new IsExistingUserRes({ existingUser: false, detail: O.none() })
9
18
  export const verifyEmailRes = new VerifyEmailRes({ principal: Fixtures.principal })
10
19
  export const resendEmailReq: ResendEmail = { userId: '123', method: 'code' }
11
- export const rpcResendEmailReq = new ResendEmailReq({ userId: '123', verifyEmail: { method: 'code' }})
12
- export const rpcResendEmailRes = new ResendEmailRes({ })
20
+ export const rpcResendEmailReq = new ResendEmailReq({
21
+ userId: '123',
22
+ verifyEmail: { method: 'code' },
23
+ })
24
+ export const rpcResendEmailRes = new ResendEmailRes({})
13
25
 
14
26
  export const rpcClientTest = L.succeed(
15
27
  UserClient,
16
28
  UserClient.of({
17
- isExistingUser: () => E.succeed({ existingUser: true }),
29
+ isExistingUser: () => E.succeed({ existingUser: true, detail: O.none() }),
18
30
  verifyEmail: () => E.succeed(verifyEmailRes),
19
31
  resendVerificationEmail: () => E.fail(Fixtures.notImplemented),
20
32
  }),
21
- )
33
+ )
@@ -1,8 +1,9 @@
1
- import { UserClient } from '@passlock/shared/dist/rpc/user.js'
2
1
  import { Effect as E, Layer as L, Layer, LogLevel, Logger, pipe } from 'effect'
3
2
  import { describe, expect, test } from 'vitest'
4
3
  import { mock } from 'vitest-mock-extended'
4
+
5
5
  import * as Fixture from './user.fixture.js'
6
+ import { UserClient } from '../rpc/user.js'
6
7
  import { UserService, UserServiceLive } from './user.js'
7
8
 
8
9
  describe('isExistingUser should', () => {
@@ -76,10 +77,7 @@ describe('resendVerificationEmail should', () => {
76
77
 
77
78
  const layers = L.merge(service, rpcClientTest)
78
79
 
79
- const effect = pipe(
80
- E.provide(assertions, layers),
81
- Logger.withMinimumLogLevel(LogLevel.None)
82
- )
80
+ const effect = pipe(E.provide(assertions, layers), Logger.withMinimumLogLevel(LogLevel.None))
83
81
 
84
82
  return E.runPromise(effect)
85
83
  })