@passlock/client 0.9.5 → 0.9.7

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 (91) hide show
  1. package/README.md +1 -1
  2. package/dist/authentication/authenticate.d.ts +6 -5
  3. package/dist/authentication/authenticate.d.ts.map +1 -1
  4. package/dist/authentication/authenticate.fixture.d.ts +8 -5
  5. package/dist/authentication/authenticate.fixture.d.ts.map +1 -1
  6. package/dist/authentication/authenticate.fixture.js +12 -7
  7. package/dist/authentication/authenticate.fixture.js.map +1 -1
  8. package/dist/authentication/authenticate.js +6 -6
  9. package/dist/authentication/authenticate.js.map +1 -1
  10. package/dist/authentication/authenticate.test.js +4 -4
  11. package/dist/capabilities/capabilities.js +3 -3
  12. package/dist/connection/connection.fixture.d.ts +1 -1
  13. package/dist/connection/connection.fixture.d.ts.map +1 -1
  14. package/dist/connection/connection.fixture.js +13 -10
  15. package/dist/connection/connection.fixture.js.map +1 -1
  16. package/dist/connection/connection.js +3 -3
  17. package/dist/effect.d.ts +21 -5
  18. package/dist/effect.d.ts.map +1 -1
  19. package/dist/effect.js +12 -9
  20. package/dist/effect.js.map +1 -1
  21. package/dist/email/email.d.ts +24 -8
  22. package/dist/email/email.d.ts.map +1 -1
  23. package/dist/email/email.fixture.d.ts +8 -8
  24. package/dist/email/email.fixture.d.ts.map +1 -1
  25. package/dist/email/email.fixture.js +12 -10
  26. package/dist/email/email.fixture.js.map +1 -1
  27. package/dist/email/email.js +5 -5
  28. package/dist/email/email.js.map +1 -1
  29. package/dist/email/email.test.js +4 -4
  30. package/dist/email/email.test.js.map +1 -1
  31. package/dist/index.d.ts +52 -37
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +21 -14
  34. package/dist/index.js.map +1 -1
  35. package/dist/registration/register.d.ts +3 -4
  36. package/dist/registration/register.d.ts.map +1 -1
  37. package/dist/registration/register.fixture.d.ts +4 -4
  38. package/dist/registration/register.fixture.d.ts.map +1 -1
  39. package/dist/registration/register.fixture.js +12 -8
  40. package/dist/registration/register.fixture.js.map +1 -1
  41. package/dist/registration/register.js +4 -13
  42. package/dist/registration/register.js.map +1 -1
  43. package/dist/social/social.d.ts +22 -0
  44. package/dist/social/social.d.ts.map +1 -0
  45. package/dist/social/social.js +29 -0
  46. package/dist/social/social.js.map +1 -0
  47. package/dist/storage/storage.d.ts +1 -1
  48. package/dist/storage/storage.d.ts.map +1 -1
  49. package/dist/storage/storage.fixture.d.ts +1 -2
  50. package/dist/storage/storage.fixture.d.ts.map +1 -1
  51. package/dist/storage/storage.fixture.js +1 -16
  52. package/dist/storage/storage.fixture.js.map +1 -1
  53. package/dist/storage/storage.js +4 -3
  54. package/dist/storage/storage.js.map +1 -1
  55. package/dist/test/fixtures.d.ts +4 -0
  56. package/dist/test/fixtures.d.ts.map +1 -1
  57. package/dist/test/fixtures.js +13 -9
  58. package/dist/test/fixtures.js.map +1 -1
  59. package/dist/user/user.d.ts +9 -2
  60. package/dist/user/user.d.ts.map +1 -1
  61. package/dist/user/user.fixture.d.ts +3 -2
  62. package/dist/user/user.fixture.d.ts.map +1 -1
  63. package/dist/user/user.fixture.js +13 -9
  64. package/dist/user/user.fixture.js.map +1 -1
  65. package/dist/user/user.js +11 -1
  66. package/dist/user/user.js.map +1 -1
  67. package/package.json +15 -14
  68. package/src/authentication/authenticate.fixture.ts +14 -7
  69. package/src/authentication/authenticate.test.ts +4 -4
  70. package/src/authentication/authenticate.ts +15 -12
  71. package/src/capabilities/capabilities.ts +3 -3
  72. package/src/connection/connection.fixture.ts +14 -11
  73. package/src/connection/connection.ts +4 -4
  74. package/src/effect.ts +28 -26
  75. package/src/email/email.fixture.ts +15 -13
  76. package/src/email/email.test.ts +4 -4
  77. package/src/email/email.ts +13 -12
  78. package/src/index.ts +91 -34
  79. package/src/registration/register.fixture.ts +12 -8
  80. package/src/registration/register.test.ts +8 -43
  81. package/src/registration/register.ts +8 -24
  82. package/src/social/social.ts +86 -0
  83. package/src/storage/storage.fixture.ts +2 -19
  84. package/src/storage/storage.ts +5 -4
  85. package/src/test/fixtures.ts +15 -10
  86. package/src/user/user.fixture.ts +14 -11
  87. package/src/user/user.ts +22 -3
  88. package/dist/exit.d.ts +0 -64
  89. package/dist/exit.d.ts.map +0 -1
  90. package/dist/exit.js +0 -106
  91. package/dist/exit.js.map +0 -1
package/src/index.ts CHANGED
@@ -7,6 +7,8 @@ import type {
7
7
  NotSupported,
8
8
  Unauthorized,
9
9
  } from '@passlock/shared/dist/error/error.js'
10
+
11
+ import type { Principal } from '@passlock/shared/dist/schema/schema.js'
10
12
  import { ErrorCode } from '@passlock/shared/dist/error/error.js'
11
13
  import { RpcConfig } from '@passlock/shared/dist/rpc/rpc.js'
12
14
  import { Effect as E, Layer as L, Layer, Option, Runtime, Scope, pipe } from 'effect'
@@ -16,30 +18,44 @@ import { ConnectionService } from './connection/connection.js'
16
18
  import { allRequirements } from './effect.js'
17
19
  import { EmailService, type VerifyRequest } from './email/email.js'
18
20
  import { type RegistrationRequest, RegistrationService } from './registration/register.js'
19
- import { type AuthType, Storage, StorageService } from './storage/storage.js'
20
- import { type Email, UserService } from './user/user.js'
21
+ import { type AuthType, Storage, StorageService, type StoredToken } from './storage/storage.js'
22
+ import { type Email, UserService, type ResendEmail } from './user/user.js'
23
+ import { SocialService, type OidcRequest } from './social/social.js'
24
+
25
+ /* Exports */
26
+
27
+ export type Options = { signal?: AbortSignal }
28
+ export type { Email } from './user/user.js'
29
+ export type { UserVerification, VerifyEmail } from '@passlock/shared/dist/schema/schema.js'
30
+ export type { RegistrationRequest } from './registration/register.js'
31
+ export type { AuthenticationRequest } from './authentication/authenticate.js'
32
+ export type { VerifyRequest } from './email/email.js'
33
+ export type { AuthType, StoredToken } from './storage/storage.js'
34
+ export type { Principal } from '@passlock/shared/dist/schema/schema.js'
21
35
 
22
36
  export { ErrorCode } from '@passlock/shared/dist/error/error.js'
23
37
 
24
38
  export class PasslockError extends Error {
25
- readonly _tag = 'PasslockError'
26
39
  readonly code: ErrorCode
40
+ readonly detail: string | undefined
27
41
 
28
- constructor(message: string, code: ErrorCode) {
42
+ constructor(message: string, code: ErrorCode, detail?: string) {
29
43
  super(message)
30
44
  this.code = code
45
+ this.detail = detail
31
46
  }
32
47
 
33
48
  static readonly isError = (error: unknown): error is PasslockError => {
34
49
  return (
35
50
  typeof error === 'object' &&
36
51
  error !== null &&
37
- '_tag' in error &&
38
- error['_tag'] === 'PasslockError'
52
+ error instanceof PasslockError
39
53
  )
40
54
  }
41
55
  }
42
56
 
57
+ /* // Exports */
58
+
43
59
  type PasslockErrors =
44
60
  | BadRequest
45
61
  | NotSupported
@@ -63,12 +79,12 @@ const transformErrors = <A, R>(
63
79
  ): E.Effect<A | PasslockError, never, R> => {
64
80
  const withErrorHandling = E.catchTags(effect, {
65
81
  NotSupported: e => E.succeed(new PasslockError(e.message, ErrorCode.NotSupported)),
66
- BadRequest: e => E.succeed(new PasslockError(e.message, ErrorCode.BadRequest)),
67
- Duplicate: e => E.succeed(new PasslockError(e.message, ErrorCode.Duplicate)),
68
- Unauthorized: e => E.succeed(new PasslockError(e.message, ErrorCode.Unauthorized)),
69
- Forbidden: e => E.succeed(new PasslockError(e.message, ErrorCode.Forbidden)),
70
- Disabled: e => E.succeed(new PasslockError(e.message, ErrorCode.Disabled)),
71
- NotFound: e => E.succeed(new PasslockError(e.message, ErrorCode.NotFound)),
82
+ BadRequest: e => E.succeed(new PasslockError(e.message, ErrorCode.BadRequest, e.detail)),
83
+ Duplicate: e => E.succeed(new PasslockError(e.message, ErrorCode.Duplicate, e.detail)),
84
+ Unauthorized: e => E.succeed(new PasslockError(e.message, ErrorCode.Unauthorized, e.detail)),
85
+ Forbidden: e => E.succeed(new PasslockError(e.message, ErrorCode.Forbidden, e.detail)),
86
+ Disabled: e => E.succeed(new PasslockError(e.message, ErrorCode.Disabled, e.detail)),
87
+ NotFound: e => E.succeed(new PasslockError(e.message, ErrorCode.NotFound, e.detail)),
72
88
  })
73
89
 
74
90
  const sandboxed = E.sandbox(withErrorHandling)
@@ -110,8 +126,7 @@ type Requirements =
110
126
  | EmailService
111
127
  | StorageService
112
128
  | Capabilities
113
-
114
- export type Options = { signal?: AbortSignal }
129
+ | SocialService
115
130
 
116
131
  export class PasslockUnsafe {
117
132
  private readonly runtime: Runtime.Runtime<Requirements>
@@ -135,56 +150,77 @@ export class PasslockUnsafe {
135
150
  )
136
151
  }
137
152
 
138
- preConnect = (options?: Options) =>
153
+ preConnect = (options?: Options): Promise<void> =>
139
154
  pipe(
140
155
  ConnectionService,
141
156
  E.flatMap(service => service.preConnect()),
142
157
  effect => Runtime.runPromise(this.runtime)(effect, options),
143
158
  )
144
159
 
145
- isPasskeySupport = () =>
160
+ isPasskeySupport = (): Promise<boolean> =>
146
161
  pipe(
147
162
  Capabilities,
148
163
  E.flatMap(service => service.isPasskeySupport),
149
164
  effect => Runtime.runPromise(this.runtime)(effect),
150
165
  )
151
166
 
152
- isExistingPasskey = (email: Email, options?: Options) =>
167
+ isExistingUser = (email: Email, options?: Options): Promise<boolean> =>
153
168
  pipe(
154
169
  UserService,
155
170
  E.flatMap(service => service.isExistingUser(email)),
156
171
  effect => this.runPromise(effect, options),
157
172
  )
158
173
 
159
- registerPasskey = (request: RegistrationRequest, options?: Options) =>
174
+ registerPasskey = (request: RegistrationRequest, options?: Options): Promise<Principal> =>
160
175
  pipe(
161
176
  RegistrationService,
162
177
  E.flatMap(service => service.registerPasskey(request)),
163
178
  effect => this.runPromise(effect, options),
164
179
  )
165
180
 
166
- authenticatePasskey = (request: AuthenticationRequest, options?: Options) =>
181
+ authenticatePasskey = (request: AuthenticationRequest, options?: Options): Promise<Principal> =>
167
182
  pipe(
168
183
  AuthenticationService,
169
184
  E.flatMap(service => service.authenticatePasskey(request)),
170
185
  effect => this.runPromise(effect, options),
171
186
  )
172
187
 
173
- verifyEmailCode = (request: VerifyRequest, options?: Options) =>
188
+ registerOidc = (request: OidcRequest, options?: Options) =>
189
+ pipe(
190
+ SocialService,
191
+ E.flatMap(service => service.registerOidc(request)),
192
+ effect => this.runPromise(effect, options),
193
+ )
194
+
195
+ authenticateOidc = (request: OidcRequest, options?: Options) =>
196
+ pipe(
197
+ SocialService,
198
+ E.flatMap(service => service.authenticateOidc(request)),
199
+ effect => this.runPromise(effect, options),
200
+ )
201
+
202
+ verifyEmailCode = (request: VerifyRequest, options?: Options): Promise<Principal> =>
174
203
  pipe(
175
204
  EmailService,
176
205
  E.flatMap(service => service.verifyEmailCode(request)),
177
206
  effect => this.runPromise(effect, options),
178
207
  )
179
208
 
180
- verifyEmailLink = (options?: Options) =>
209
+ resendVerificationEmail = (request: ResendEmail, options?: Options): Promise<void> =>
210
+ pipe(
211
+ UserService,
212
+ E.flatMap(service => service.resendVerificationEmail(request)),
213
+ effect => this.runPromise(effect, options),
214
+ )
215
+
216
+ verifyEmailLink = (options?: Options): Promise<Principal> =>
181
217
  pipe(
182
218
  EmailService,
183
219
  E.flatMap(service => service.verifyEmailLink()),
184
220
  effect => this.runPromise(effect, options),
185
221
  )
186
222
 
187
- getSessionToken = (authType: AuthType) =>
223
+ getSessionToken = (authType: AuthType): StoredToken | undefined =>
188
224
  pipe(
189
225
  StorageService,
190
226
  E.flatMap(service => service.getToken(authType).pipe(effect => E.option(effect))),
@@ -192,11 +228,11 @@ export class PasslockUnsafe {
192
228
  effect => Runtime.runSync(this.runtime)(effect),
193
229
  )
194
230
 
195
- clearExpiredTokens = () =>
231
+ clearExpiredTokens = (): void =>
196
232
  pipe(
197
233
  StorageService,
198
234
  E.flatMap(service => service.clearExpiredTokens),
199
- effect => Runtime.runPromise(this.runtime)(effect),
235
+ effect => Runtime.runSync(this.runtime)(effect),
200
236
  )
201
237
  }
202
238
 
@@ -221,56 +257,77 @@ export class Passlock {
221
257
  )
222
258
  }
223
259
 
224
- preConnect = (options?: Options) =>
260
+ preConnect = (options?: Options): Promise<void | PasslockError> =>
225
261
  pipe(
226
262
  ConnectionService,
227
263
  E.flatMap(service => service.preConnect()),
228
264
  effect => this.runPromise(effect, options),
229
265
  )
230
266
 
231
- isPasskeySupport = () =>
267
+ isPasskeySupport = (): Promise<boolean> =>
232
268
  pipe(
233
269
  Capabilities,
234
270
  E.flatMap(service => service.isPasskeySupport),
235
271
  effect => Runtime.runPromise(this.runtime)(effect),
236
272
  )
237
273
 
238
- isExistingPasskey = (email: Email, options?: Options) =>
274
+ isExistingUser = (email: Email, options?: Options): Promise<boolean | PasslockError> =>
239
275
  pipe(
240
276
  UserService,
241
277
  E.flatMap(service => service.isExistingUser(email)),
242
278
  effect => this.runPromise(effect, options),
243
279
  )
244
280
 
245
- registerPasskey = (request: RegistrationRequest, options?: Options) =>
281
+ registerPasskey = (request: RegistrationRequest, options?: Options): Promise<Principal | PasslockError> =>
246
282
  pipe(
247
283
  RegistrationService,
248
284
  E.flatMap(service => service.registerPasskey(request)),
249
285
  effect => this.runPromise(effect, options),
250
286
  )
251
287
 
252
- authenticatePasskey = (request: AuthenticationRequest = {}, options?: Options) =>
288
+ authenticatePasskey = (request: AuthenticationRequest = {}, options?: Options): Promise<Principal | PasslockError> =>
253
289
  pipe(
254
290
  AuthenticationService,
255
291
  E.flatMap(service => service.authenticatePasskey(request)),
256
292
  effect => this.runPromise(effect, options),
257
293
  )
258
294
 
259
- verifyEmailCode = (request: VerifyRequest, options?: Options) =>
295
+ registerOidc = (request: OidcRequest, options?: Options) =>
296
+ pipe(
297
+ SocialService,
298
+ E.flatMap(service => service.registerOidc(request)),
299
+ effect => this.runPromise(effect, options),
300
+ )
301
+
302
+ authenticateOidc = (request: OidcRequest, options?: Options) =>
303
+ pipe(
304
+ SocialService,
305
+ E.flatMap(service => service.authenticateOidc(request)),
306
+ effect => this.runPromise(effect, options),
307
+ )
308
+
309
+ verifyEmailCode = (request: VerifyRequest, options?: Options): Promise<Principal | PasslockError> =>
260
310
  pipe(
261
311
  EmailService,
262
312
  E.flatMap(service => service.verifyEmailCode(request)),
263
313
  effect => this.runPromise(effect, options),
264
314
  )
265
315
 
266
- verifyEmailLink = (options?: Options) =>
316
+ verifyEmailLink = (options?: Options): Promise<Principal | PasslockError> =>
267
317
  pipe(
268
318
  EmailService,
269
319
  E.flatMap(service => service.verifyEmailLink()),
270
320
  effect => this.runPromise(effect, options),
271
321
  )
272
322
 
273
- getSessionToken = (authType: AuthType) =>
323
+ resendVerificationEmail = (request: ResendEmail, options?: Options): Promise<void | PasslockError> =>
324
+ pipe(
325
+ UserService,
326
+ E.flatMap(service => service.resendVerificationEmail(request)),
327
+ effect => this.runPromise(effect, options),
328
+ )
329
+
330
+ getSessionToken = (authType: AuthType): StoredToken | undefined =>
274
331
  pipe(
275
332
  StorageService,
276
333
  E.flatMap(service => service.getToken(authType).pipe(effect => E.option(effect))),
@@ -278,10 +335,10 @@ export class Passlock {
278
335
  effect => Runtime.runSync(this.runtime)(effect),
279
336
  )
280
337
 
281
- clearExpiredTokens = () =>
338
+ clearExpiredTokens = (): void =>
282
339
  pipe(
283
340
  StorageService,
284
341
  E.flatMap(service => service.clearExpiredTokens),
285
- effect => Runtime.runPromise(this.runtime)(effect),
342
+ effect => Runtime.runSync(this.runtime)(effect),
286
343
  )
287
344
  }
@@ -1,4 +1,3 @@
1
- import { BadRequest } from '@passlock/shared/dist/error/error.js'
2
1
  import {
3
2
  OptionsReq,
4
3
  OptionsRes,
@@ -11,6 +10,7 @@ import { Effect as E, Layer as L } from 'effect'
11
10
  import { CreateCredential, type RegistrationRequest } from './register.js'
12
11
  import * as Fixtures from '../test/fixtures.js'
13
12
  import { UserService } from '../user/user.js'
13
+ import { PreConnectRes } from '@passlock/shared/dist/rpc/connection.js'
14
14
 
15
15
  export const session = 'session'
16
16
  export const token = 'token'
@@ -20,8 +20,8 @@ export const expireAt = Date.now() + 10000
20
20
 
21
21
  export const registrationRequest: RegistrationRequest = {
22
22
  email: 'jdoe@gmail.com',
23
- firstName: 'john',
24
- lastName: 'doe',
23
+ givenName: 'john',
24
+ familyName: 'doe',
25
25
  }
26
26
 
27
27
  export const optionsReq = new OptionsReq(registrationRequest)
@@ -70,19 +70,23 @@ export const userServiceTest = L.succeed(
70
70
  UserService,
71
71
  UserService.of({
72
72
  isExistingUser: () => E.succeed(false),
73
+ resendVerificationEmail: () => E.succeed(true)
73
74
  }),
74
75
  )
75
76
 
76
77
  export const rpcClientTest = L.succeed(
77
78
  RpcClient,
78
79
  RpcClient.of({
79
- preConnect: () => E.succeed({ warmed: true }),
80
- isExistingUser: () => E.succeed({ existingUser: true }),
81
- verifyEmail: () => E.succeed({ verified: true }),
80
+ preConnect: () => E.succeed(new PreConnectRes({ warmed: true })),
81
+ isExistingUser: () => E.fail(Fixtures.notImplemented),
82
+ verifyEmail: () => E.fail(Fixtures.notImplemented),
82
83
  getRegistrationOptions: () => E.succeed(optionsRes),
83
84
  verifyRegistrationCredential: () => E.succeed(verificationRes),
84
- getAuthenticationOptions: () => E.fail(new BadRequest({ message: 'Not implemeneted' })),
85
- verifyAuthenticationCredential: () => E.succeed({ principal: Fixtures.principal }),
85
+ getAuthenticationOptions: () => E.fail(Fixtures.notImplemented),
86
+ verifyAuthenticationCredential: () => E.fail(Fixtures.notImplemented),
87
+ registerOidc: () => E.fail(Fixtures.notImplemented),
88
+ authenticateOidc: () => E.fail(Fixtures.notImplemented),
89
+ resendVerificationEmail: () => E.fail(Fixtures.notImplemented),
86
90
  }),
87
91
  )
88
92
 
@@ -29,41 +29,6 @@ describe('register should', () => {
29
29
  return E.runPromise(effect)
30
30
  })
31
31
 
32
- test('check if the user is already registered', async () => {
33
- const assertions = E.gen(function* (_) {
34
- const service = yield* _(RegistrationService)
35
- yield* _(service.registerPasskey(Fixture.registrationRequest))
36
-
37
- const userService = yield* _(UserService)
38
- expect(userService.isExistingUser).toHaveBeenCalled()
39
- })
40
-
41
- const userServiceTest = L.effect(
42
- UserService,
43
- E.sync(() => {
44
- const userMock = mock<UserService>()
45
-
46
- userMock.isExistingUser.mockReturnValue(E.succeed(false))
47
-
48
- return userMock
49
- }),
50
- )
51
-
52
- const service = pipe(
53
- RegistrationServiceLive,
54
- L.provide(Fixture.createCredentialTest),
55
- L.provide(Fixture.capabilitiesTest),
56
- L.provide(Fixture.storageServiceTest),
57
- L.provide(Fixture.rpcClientTest),
58
- L.provide(userServiceTest),
59
- )
60
-
61
- const layers = Layer.merge(service, userServiceTest)
62
- const effect = pipe(E.provide(assertions, layers), Logger.withMinimumLogLevel(LogLevel.None))
63
-
64
- return E.runPromise(effect)
65
- })
66
-
67
32
  test('pass the registration data to the backend', async () => {
68
33
  const assertions = E.gen(function* (_) {
69
34
  const service = yield* _(RegistrationService)
@@ -145,14 +110,14 @@ describe('register should', () => {
145
110
  expect(error).toBeInstanceOf(Duplicate)
146
111
  })
147
112
 
148
- const userServiceTest = L.effect(
149
- UserService,
113
+ const rpcClientTest = L.effect(
114
+ RpcClient,
150
115
  E.sync(() => {
151
- const userMock = mock<UserService>()
116
+ const rpcMock = mock<RpcClient['Type']>()
152
117
 
153
- userMock.isExistingUser.mockReturnValue(E.succeed(true))
118
+ rpcMock.getRegistrationOptions.mockReturnValue(E.fail(new Duplicate({ message: 'User already exists' })))
154
119
 
155
- return userMock
120
+ return rpcMock
156
121
  }),
157
122
  )
158
123
 
@@ -161,11 +126,11 @@ describe('register should', () => {
161
126
  L.provide(Fixture.createCredentialTest),
162
127
  L.provide(Fixture.capabilitiesTest),
163
128
  L.provide(Fixture.storageServiceTest),
164
- L.provide(Fixture.rpcClientTest),
165
- L.provide(userServiceTest),
129
+ L.provide(Fixture.userServiceTest),
130
+ L.provide(rpcClientTest),
166
131
  )
167
132
 
168
- const layers = Layer.merge(service, userServiceTest)
133
+ const layers = Layer.merge(service, rpcClientTest)
169
134
  const effect = pipe(E.provide(assertions, layers), Logger.withMinimumLogLevel(LogLevel.None))
170
135
 
171
136
  return E.runPromise(effect)
@@ -25,18 +25,18 @@ import { UserService } from '../user/user.js'
25
25
 
26
26
  export type RegistrationRequest = {
27
27
  email: string
28
- firstName: string
29
- lastName: string
28
+ givenName: string
29
+ familyName: string
30
30
  userVerification?: UserVerification
31
31
  verifyEmail?: VerifyEmail
32
- redirectUrl?: string
33
32
  }
34
33
 
35
34
  /* Dependencies */
36
35
 
37
36
  export type CreateCredential = (
38
- options: CredentialCreationOptions,
37
+ request: CredentialCreationOptions,
39
38
  ) => E.Effect<RegistrationCredential, InternalBrowserError | Duplicate>
39
+
40
40
  export const CreateCredential = Context.GenericTag<CreateCredential>('@services/Create')
41
41
 
42
42
  /* Errors */
@@ -55,12 +55,12 @@ export const RegistrationService = Context.GenericTag<RegistrationService>(
55
55
 
56
56
  /* Utilities */
57
57
 
58
- const fetchOptions = (req: OptionsReq) => {
58
+ const fetchOptions = (request: OptionsReq) => {
59
59
  return E.gen(function* (_) {
60
60
  yield* _(E.logDebug('Making request'))
61
61
 
62
62
  const rpcClient = yield* _(RpcClient)
63
- const { publicKey, session } = yield* _(rpcClient.getRegistrationOptions(req))
63
+ const { publicKey, session } = yield* _(rpcClient.getRegistrationOptions(request))
64
64
 
65
65
  yield* _(E.logDebug('Converting Passlock options to CredentialCreationOptions'))
66
66
  const options = yield* _(toCreationOptions({ publicKey }))
@@ -82,30 +82,17 @@ const toCreationOptions = (jsonOptions: CredentialCreationOptionsJSON) => {
82
82
  )
83
83
  }
84
84
 
85
- const verifyCredential = (req: VerificationReq) => {
85
+ const verifyCredential = (request: VerificationReq) => {
86
86
  return E.gen(function* (_) {
87
87
  yield* _(E.logDebug('Making request'))
88
88
 
89
89
  const rpcClient = yield* _(RpcClient)
90
- const { principal } = yield* _(rpcClient.verifyRegistrationCredential(req))
90
+ const { principal } = yield* _(rpcClient.verifyRegistrationCredential(request))
91
91
 
92
92
  return principal
93
93
  })
94
94
  }
95
95
 
96
- const isNewUser = (email: string) => {
97
- return pipe(
98
- UserService,
99
- E.flatMap(service => service.isExistingUser({ email })),
100
- E.catchTag('BadRequest', () => E.unit),
101
- E.flatMap(isExistingUser => {
102
- return isExistingUser
103
- ? new Duplicate({ message: 'User already has a passkey registered' })
104
- : E.unit
105
- }),
106
- )
107
- }
108
-
109
96
  /* Effects */
110
97
 
111
98
  type Dependencies = Capabilities | CreateCredential | StorageService | UserService | RpcClient
@@ -118,9 +105,6 @@ export const registerPasskey = (
118
105
  const capabilities = yield* _(Capabilities)
119
106
  yield* _(capabilities.passkeySupport)
120
107
 
121
- yield* _(E.logInfo('Checking if already registered'))
122
- yield* _(isNewUser(request.email))
123
-
124
108
  yield* _(E.logInfo('Fetching registration options from Passlock'))
125
109
  const { options, session } = yield* _(fetchOptions(new OptionsReq(request)))
126
110
 
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Passkey authentication effects
3
+ */
4
+ import {
5
+ type BadRequest,
6
+ type NotSupported
7
+ } from '@passlock/shared/dist/error/error.js'
8
+ import { RpcClient } from '@passlock/shared/dist/rpc/rpc.js'
9
+ import { AuthenticateOidcErrors, AuthenticateOidcReq, RegisterOidcErrors, RegisterOidcReq } from '@passlock/shared/dist/rpc/social.js'
10
+ import type {
11
+ Principal
12
+ } from '@passlock/shared/dist/schema/schema.js'
13
+ import { Context, Effect as E, Layer, flow } from 'effect'
14
+
15
+ /* Requests */
16
+
17
+ export type OidcRequest = { provider: 'google', idToken: string }
18
+
19
+ /* Errors */
20
+
21
+ export type RegistrationErrors = NotSupported | BadRequest | RegisterOidcErrors
22
+
23
+ export type AuthenticationErrors = NotSupported | BadRequest | AuthenticateOidcErrors
24
+
25
+ /* Service */
26
+
27
+ export type SocialService = {
28
+ registerOidc: (data: OidcRequest) => E.Effect<Principal, RegistrationErrors>
29
+ authenticateOidc: (data: OidcRequest) => E.Effect<Principal, AuthenticationErrors>
30
+ }
31
+
32
+ export const SocialService = Context.GenericTag<SocialService>(
33
+ '@services/SocialService',
34
+ )
35
+
36
+ /* Effects */
37
+
38
+ type Dependencies = RpcClient
39
+
40
+ export const registerOidc = (
41
+ request: OidcRequest,
42
+ ): E.Effect<Principal, RegistrationErrors, Dependencies> => {
43
+ return E.gen(function* (_) {
44
+ yield* _(E.logInfo('Registering social account'))
45
+
46
+ const rpcClient = yield* _(RpcClient)
47
+
48
+ const { principal } = yield* _(
49
+ rpcClient.registerOidc(new RegisterOidcReq(request))
50
+ )
51
+
52
+ return principal
53
+ })
54
+ }
55
+
56
+ export const authenticateOidc = (
57
+ request: OidcRequest,
58
+ ): E.Effect<Principal, AuthenticationErrors, Dependencies> => {
59
+ return E.gen(function* (_) {
60
+ yield* _(E.logInfo('Authenticating with social account'))
61
+
62
+ const rpcClient = yield* _(RpcClient)
63
+
64
+ const { principal } = yield* _(
65
+ rpcClient.authenticateOidc(new AuthenticateOidcReq(request))
66
+ )
67
+
68
+ return principal
69
+ })
70
+ }
71
+
72
+ /* Live */
73
+
74
+ /* v8 ignore start */
75
+ export const SocialServiceLive = Layer.effect(
76
+ SocialService,
77
+ E.gen(function* (_) {
78
+ const context = yield* _(E.context<RpcClient>())
79
+
80
+ return SocialService.of({
81
+ registerOidc: flow(registerOidc, E.provide(context)),
82
+ authenticateOidc: flow(authenticateOidc, E.provide(context))
83
+ })
84
+ }),
85
+ )
86
+ /* v8 ignore stop */
@@ -1,26 +1,7 @@
1
- import type { Principal } from '@passlock/shared/dist/schema/schema.js'
2
1
  import { Effect as E, Layer, pipe } from 'effect'
3
2
  import { mock } from 'vitest-mock-extended'
4
3
  import { Storage, StorageServiceLive } from './storage.js'
5
4
 
6
- // Frontend receives dates as objects
7
- export const principal: Principal = {
8
- token: 'token',
9
- subject: {
10
- id: '1',
11
- email: 'john.doe@gmail.com',
12
- firstName: 'john',
13
- lastName: 'doe',
14
- emailVerified: false,
15
- },
16
- authStatement: {
17
- authType: 'passkey',
18
- userVerified: false,
19
- authTimestamp: new Date(0),
20
- },
21
- expireAt: new Date(100),
22
- }
23
-
24
5
  const storageTest = Layer.effect(
25
6
  Storage,
26
7
  E.sync(() => mock<Storage>()),
@@ -31,3 +12,5 @@ export const testLayers = (storage: Layer.Layer<Storage> = storageTest) => {
31
12
 
32
13
  return Layer.merge(storage, storageService)
33
14
  }
15
+
16
+ export { principal } from '../test/fixtures.js'
@@ -8,7 +8,7 @@ import type { NoSuchElementException } from 'effect/Cause'
8
8
 
9
9
  /* Requests */
10
10
 
11
- export type AuthType = 'email' | 'passkey'
11
+ export type AuthType = 'email' | 'passkey' | 'google'
12
12
 
13
13
  export type StoredToken = {
14
14
  token: string
@@ -71,7 +71,7 @@ export const storeToken = (principal: Principal): E.Effect<void, never, Storage>
71
71
  const compressed = compressToken(principal)
72
72
  const key = buildKey(principal.authStatement.authType)
73
73
  localStorage.setItem(key, compressed)
74
- }).pipe(E.orElse(() => E.unit)) // We dont care if it fails
74
+ }).pipe(E.orElse(() => E.void)) // We dont care if it fails
75
75
 
76
76
  return yield* _(storeEffect)
77
77
  })
@@ -134,8 +134,8 @@ export const clearExpiredToken = (authType: AuthType): E.Effect<void, never, Sto
134
134
  return pipe(
135
135
  effect,
136
136
  E.match({
137
- onSuccess: () => E.unit,
138
- onFailure: () => E.unit,
137
+ onSuccess: () => E.void,
138
+ onFailure: () => E.void,
139
139
  }),
140
140
  )
141
141
  }
@@ -143,6 +143,7 @@ export const clearExpiredToken = (authType: AuthType): E.Effect<void, never, Sto
143
143
  export const clearExpiredTokens: E.Effect<void, never, Storage> = E.all([
144
144
  clearExpiredToken('passkey'),
145
145
  clearExpiredToken('email'),
146
+ clearExpiredToken('google'),
146
147
  ])
147
148
 
148
149
  /* Live */