@passlock/client 0.9.6 → 0.9.8
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.
- package/README.md +3 -3
- package/dist/authentication/authenticate.d.ts +6 -5
- package/dist/authentication/authenticate.d.ts.map +1 -1
- package/dist/authentication/authenticate.fixture.d.ts +8 -5
- package/dist/authentication/authenticate.fixture.d.ts.map +1 -1
- package/dist/authentication/authenticate.fixture.js +12 -7
- package/dist/authentication/authenticate.fixture.js.map +1 -1
- package/dist/authentication/authenticate.js +6 -6
- package/dist/authentication/authenticate.js.map +1 -1
- package/dist/authentication/authenticate.test.js +4 -4
- package/dist/capabilities/capabilities.js +3 -3
- package/dist/connection/connection.fixture.d.ts +1 -1
- package/dist/connection/connection.fixture.d.ts.map +1 -1
- package/dist/connection/connection.fixture.js +13 -10
- package/dist/connection/connection.fixture.js.map +1 -1
- package/dist/connection/connection.js +3 -3
- package/dist/effect.d.ts +21 -5
- package/dist/effect.d.ts.map +1 -1
- package/dist/effect.js +12 -9
- package/dist/effect.js.map +1 -1
- package/dist/email/email.d.ts +24 -8
- package/dist/email/email.d.ts.map +1 -1
- package/dist/email/email.fixture.d.ts +8 -8
- package/dist/email/email.fixture.d.ts.map +1 -1
- package/dist/email/email.fixture.js +12 -10
- package/dist/email/email.fixture.js.map +1 -1
- package/dist/email/email.js +5 -5
- package/dist/email/email.js.map +1 -1
- package/dist/email/email.test.js +4 -4
- package/dist/email/email.test.js.map +1 -1
- package/dist/index.d.ts +52 -37
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -14
- package/dist/index.js.map +1 -1
- package/dist/registration/register.d.ts +3 -4
- package/dist/registration/register.d.ts.map +1 -1
- package/dist/registration/register.fixture.d.ts +4 -4
- package/dist/registration/register.fixture.d.ts.map +1 -1
- package/dist/registration/register.fixture.js +12 -8
- package/dist/registration/register.fixture.js.map +1 -1
- package/dist/registration/register.js +4 -13
- package/dist/registration/register.js.map +1 -1
- package/dist/social/social.d.ts +22 -0
- package/dist/social/social.d.ts.map +1 -0
- package/dist/social/social.js +29 -0
- package/dist/social/social.js.map +1 -0
- package/dist/storage/storage.d.ts +1 -1
- package/dist/storage/storage.d.ts.map +1 -1
- package/dist/storage/storage.fixture.d.ts +1 -2
- package/dist/storage/storage.fixture.d.ts.map +1 -1
- package/dist/storage/storage.fixture.js +1 -16
- package/dist/storage/storage.fixture.js.map +1 -1
- package/dist/storage/storage.js +4 -3
- package/dist/storage/storage.js.map +1 -1
- package/dist/test/fixtures.d.ts +4 -0
- package/dist/test/fixtures.d.ts.map +1 -1
- package/dist/test/fixtures.js +13 -9
- package/dist/test/fixtures.js.map +1 -1
- package/dist/user/user.d.ts +9 -2
- package/dist/user/user.d.ts.map +1 -1
- package/dist/user/user.fixture.d.ts +3 -2
- package/dist/user/user.fixture.d.ts.map +1 -1
- package/dist/user/user.fixture.js +13 -9
- package/dist/user/user.fixture.js.map +1 -1
- package/dist/user/user.js +11 -1
- package/dist/user/user.js.map +1 -1
- package/package.json +14 -13
- package/src/authentication/authenticate.fixture.ts +14 -7
- package/src/authentication/authenticate.test.ts +4 -4
- package/src/authentication/authenticate.ts +15 -12
- package/src/capabilities/capabilities.ts +3 -3
- package/src/connection/connection.fixture.ts +14 -11
- package/src/connection/connection.ts +4 -4
- package/src/effect.ts +28 -26
- package/src/email/email.fixture.ts +15 -13
- package/src/email/email.test.ts +4 -4
- package/src/email/email.ts +13 -12
- package/src/index.ts +91 -34
- package/src/registration/register.fixture.ts +12 -8
- package/src/registration/register.test.ts +8 -43
- package/src/registration/register.ts +8 -24
- package/src/social/social.ts +86 -0
- package/src/storage/storage.fixture.ts +2 -19
- package/src/storage/storage.ts +5 -4
- package/src/test/fixtures.ts +15 -10
- package/src/user/user.fixture.ts +14 -11
- package/src/user/user.ts +22 -3
- package/dist/exit.d.ts +0 -64
- package/dist/exit.d.ts.map +0 -1
- package/dist/exit.js +0 -106
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
24
|
-
|
|
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.
|
|
81
|
-
verifyEmail: () => E.
|
|
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(
|
|
85
|
-
verifyAuthenticationCredential: () => E.
|
|
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
|
|
149
|
-
|
|
113
|
+
const rpcClientTest = L.effect(
|
|
114
|
+
RpcClient,
|
|
150
115
|
E.sync(() => {
|
|
151
|
-
const
|
|
116
|
+
const rpcMock = mock<RpcClient['Type']>()
|
|
152
117
|
|
|
153
|
-
|
|
118
|
+
rpcMock.getRegistrationOptions.mockReturnValue(E.fail(new Duplicate({ message: 'User already exists' })))
|
|
154
119
|
|
|
155
|
-
return
|
|
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.
|
|
165
|
-
L.provide(
|
|
129
|
+
L.provide(Fixture.userServiceTest),
|
|
130
|
+
L.provide(rpcClientTest),
|
|
166
131
|
)
|
|
167
132
|
|
|
168
|
-
const layers = Layer.merge(service,
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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 = (
|
|
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(
|
|
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 = (
|
|
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(
|
|
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'
|
package/src/storage/storage.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
138
|
-
onFailure: () => E.
|
|
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 */
|