@passlock/client 0.9.21 → 0.9.23
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 +125 -0
- package/dist/authentication/authenticate.d.ts +15 -15
- package/dist/authentication/authenticate.fixture.d.ts +20 -6
- package/dist/authentication/authenticate.fixture.js +7 -5
- package/dist/authentication/authenticate.fixture.js.map +1 -1
- package/dist/authentication/authenticate.js +19 -8
- package/dist/authentication/authenticate.js.map +1 -1
- package/dist/capabilities/capabilities.d.ts +8 -4
- package/dist/capabilities/capabilities.js +10 -1
- package/dist/capabilities/capabilities.js.map +1 -1
- package/dist/connection/connection.d.ts +11 -7
- package/dist/connection/connection.fixture.d.ts +2 -2
- package/dist/connection/connection.fixture.js +2 -1
- package/dist/connection/connection.fixture.js.map +1 -1
- package/dist/connection/connection.js +12 -3
- package/dist/connection/connection.js.map +1 -1
- package/dist/effect.d.ts +22 -45
- package/dist/effect.js +55 -51
- package/dist/effect.js.map +1 -1
- package/dist/email/email.d.ts +38 -11
- package/dist/email/email.fixture.d.ts +19 -5
- package/dist/email/email.fixture.js +4 -3
- package/dist/email/email.fixture.js.map +1 -1
- package/dist/email/email.js +43 -7
- package/dist/email/email.js.map +1 -1
- package/dist/event/event.d.ts +3 -1
- package/dist/event/event.js +3 -0
- package/dist/event/event.js.map +1 -1
- package/dist/index.d.ts +105 -27
- package/dist/index.js +101 -50
- package/dist/index.js.map +1 -1
- package/dist/logging/eventLogger.d.ts +13 -1
- package/dist/logging/eventLogger.js +13 -0
- package/dist/logging/eventLogger.js.map +1 -1
- package/dist/registration/register.d.ts +18 -21
- package/dist/registration/register.fixture.d.ts +19 -5
- package/dist/registration/register.fixture.js +14 -7
- package/dist/registration/register.fixture.js.map +1 -1
- package/dist/registration/register.js +18 -9
- package/dist/registration/register.js.map +1 -1
- package/dist/rpc/authentication.d.ts +0 -1
- package/dist/rpc/authentication.js +1 -0
- package/dist/rpc/authentication.js.map +1 -1
- package/dist/rpc/client.d.ts +4 -1
- package/dist/rpc/client.js +12 -2
- package/dist/rpc/client.js.map +1 -1
- package/dist/rpc/config.d.ts +0 -1
- package/dist/rpc/connection.d.ts +0 -1
- package/dist/rpc/connection.js +1 -0
- package/dist/rpc/connection.js.map +1 -1
- package/dist/rpc/registration.d.ts +0 -1
- package/dist/rpc/registration.js +1 -0
- package/dist/rpc/registration.js.map +1 -1
- package/dist/rpc/social.d.ts +0 -1
- package/dist/rpc/social.js +1 -0
- package/dist/rpc/social.js.map +1 -1
- package/dist/rpc/user.d.ts +0 -1
- package/dist/rpc/user.js +1 -0
- package/dist/rpc/user.js.map +1 -1
- package/dist/social/social.d.ts +16 -23
- package/dist/social/social.fixture.d.ts +21 -9
- package/dist/social/social.fixture.js +8 -14
- package/dist/social/social.fixture.js.map +1 -1
- package/dist/social/social.js +14 -10
- package/dist/social/social.js.map +1 -1
- package/dist/storage/storage.d.ts +40 -12
- package/dist/storage/storage.fixture.d.ts +2 -2
- package/dist/storage/storage.fixture.js +2 -2
- package/dist/storage/storage.fixture.js.map +1 -1
- package/dist/storage/storage.js +48 -15
- package/dist/storage/storage.js.map +1 -1
- package/dist/test/fixtures.d.ts +1 -2
- package/dist/test/fixtures.js +20 -5
- package/dist/test/fixtures.js.map +1 -1
- package/dist/user/user.d.ts +8 -5
- package/dist/user/user.fixture.d.ts +2 -2
- package/dist/user/user.fixture.js +9 -5
- package/dist/user/user.fixture.js.map +1 -1
- package/dist/user/user.js +9 -3
- package/dist/user/user.js.map +1 -1
- package/dist/version.d.ts +1 -2
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +39 -33
- package/src/authentication/authenticate.fixture.ts +8 -7
- package/src/authentication/authenticate.test.ts +59 -17
- package/src/authentication/authenticate.ts +34 -32
- package/src/capabilities/capabilities.ts +9 -8
- package/src/connection/connection.fixture.ts +2 -1
- package/src/connection/connection.test.ts +3 -3
- package/src/connection/connection.ts +9 -8
- package/src/effect.ts +129 -128
- package/src/email/email.fixture.ts +4 -3
- package/src/email/email.test.ts +4 -4
- package/src/email/email.ts +24 -16
- package/src/index.ts +225 -169
- package/src/logging/eventLogger.test.ts +1 -1
- package/src/logging/eventLogger.ts +2 -2
- package/src/registration/register.fixture.ts +14 -8
- package/src/registration/register.test.ts +13 -9
- package/src/registration/register.ts +37 -34
- package/src/rpc/authentication.ts +31 -0
- package/src/rpc/client.ts +173 -0
- package/src/rpc/config.ts +18 -0
- package/src/rpc/connection.ts +24 -0
- package/src/rpc/registration.ts +31 -0
- package/src/rpc/social.ts +36 -0
- package/src/rpc/user.ts +42 -0
- package/src/social/social.fixture.ts +10 -18
- package/src/social/social.test.ts +13 -29
- package/src/social/social.ts +20 -47
- package/src/storage/storage.fixture.ts +3 -4
- package/src/storage/storage.test.ts +28 -19
- package/src/storage/storage.ts +36 -36
- package/src/test/fixtures.ts +21 -6
- package/src/user/user.fixture.ts +17 -7
- package/src/user/user.test.ts +2 -5
- package/src/user/user.ts +13 -9
- package/src/version.ts +1 -0
- package/dist/authentication/authenticate.d.ts.map +0 -1
- package/dist/authentication/authenticate.fixture.d.ts.map +0 -1
- package/dist/authentication/authenticate.test.d.ts +0 -2
- package/dist/authentication/authenticate.test.d.ts.map +0 -1
- package/dist/authentication/authenticate.test.js +0 -111
- package/dist/authentication/authenticate.test.js.map +0 -1
- package/dist/capabilities/capabilities.d.ts.map +0 -1
- package/dist/config.d.ts +0 -18
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -20
- package/dist/config.js.map +0 -1
- package/dist/connection/connection.d.ts.map +0 -1
- package/dist/connection/connection.fixture.d.ts.map +0 -1
- package/dist/connection/connection.test.d.ts +0 -2
- package/dist/connection/connection.test.d.ts.map +0 -1
- package/dist/connection/connection.test.js +0 -36
- package/dist/connection/connection.test.js.map +0 -1
- package/dist/effect.d.ts.map +0 -1
- package/dist/email/email.d.ts.map +0 -1
- package/dist/email/email.fixture.d.ts.map +0 -1
- package/dist/email/email.test.d.ts +0 -2
- package/dist/email/email.test.d.ts.map +0 -1
- package/dist/email/email.test.js +0 -99
- package/dist/email/email.test.js.map +0 -1
- package/dist/event/event.d.ts.map +0 -1
- package/dist/event/event.node.test.d.ts +0 -2
- package/dist/event/event.node.test.d.ts.map +0 -1
- package/dist/event/event.node.test.js +0 -13
- package/dist/event/event.node.test.js.map +0 -1
- package/dist/event/event.test.d.ts +0 -2
- package/dist/event/event.test.d.ts.map +0 -1
- package/dist/event/event.test.js +0 -30
- package/dist/event/event.test.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/logging/eventLogger.d.ts.map +0 -1
- package/dist/logging/eventLogger.test.d.ts +0 -2
- package/dist/logging/eventLogger.test.d.ts.map +0 -1
- package/dist/logging/eventLogger.test.js +0 -67
- package/dist/logging/eventLogger.test.js.map +0 -1
- package/dist/registration/register.d.ts.map +0 -1
- package/dist/registration/register.fixture.d.ts.map +0 -1
- package/dist/registration/register.test.d.ts +0 -2
- package/dist/registration/register.test.d.ts.map +0 -1
- package/dist/registration/register.test.js +0 -104
- package/dist/registration/register.test.js.map +0 -1
- package/dist/rpc/authentication.d.ts.map +0 -1
- package/dist/rpc/client.d.ts.map +0 -1
- package/dist/rpc/config.d.ts.map +0 -1
- package/dist/rpc/connection.d.ts.map +0 -1
- package/dist/rpc/registration.d.ts.map +0 -1
- package/dist/rpc/social.d.ts.map +0 -1
- package/dist/rpc/user.d.ts.map +0 -1
- package/dist/social/social.d.ts.map +0 -1
- package/dist/social/social.fixture.d.ts.map +0 -1
- package/dist/social/social.test.d.ts +0 -2
- package/dist/social/social.test.d.ts.map +0 -1
- package/dist/social/social.test.js +0 -110
- package/dist/social/social.test.js.map +0 -1
- package/dist/storage/storage.d.ts.map +0 -1
- package/dist/storage/storage.fixture.d.ts.map +0 -1
- package/dist/storage/storage.test.d.ts +0 -2
- package/dist/storage/storage.test.d.ts.map +0 -1
- package/dist/storage/storage.test.js +0 -120
- package/dist/storage/storage.test.js.map +0 -1
- package/dist/test/fixtures.d.ts.map +0 -1
- package/dist/user/user.d.ts.map +0 -1
- package/dist/user/user.fixture.d.ts.map +0 -1
- package/dist/user/user.test.d.ts +0 -2
- package/dist/user/user.test.d.ts.map +0 -1
- package/dist/user/user.test.js +0 -56
- package/dist/user/user.test.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/src/config.ts +0 -42
package/src/social/social.ts
CHANGED
|
@@ -1,51 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Passkey authentication effects
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
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'
|
|
4
|
+
import { type BadRequest, type NotSupported } from '@passlock/shared/dist/error/error.js'
|
|
5
|
+
import * as RPC from '@passlock/shared/dist/rpc/social.js'
|
|
6
|
+
import type { Principal } from '@passlock/shared/dist/schema/principal.js'
|
|
13
7
|
import { Context, Effect as E, Layer, flow } from 'effect'
|
|
8
|
+
import { SocialClient } from '../rpc/social.js'
|
|
14
9
|
|
|
15
10
|
/* Requests */
|
|
16
11
|
|
|
17
12
|
export type Provider = 'apple' | 'google'
|
|
18
13
|
|
|
19
|
-
export type RegisterOidcReq =
|
|
20
|
-
provider: Provider
|
|
21
|
-
idToken: string
|
|
22
|
-
nonce: string
|
|
23
|
-
givenName?: string
|
|
24
|
-
familyName?: string
|
|
25
|
-
}
|
|
14
|
+
export type RegisterOidcReq = RPC.RegisterOidcReq
|
|
26
15
|
|
|
27
|
-
export type AuthenticateOidcReq =
|
|
28
|
-
provider: Provider
|
|
29
|
-
idToken: string
|
|
30
|
-
nonce: string
|
|
31
|
-
}
|
|
16
|
+
export type AuthenticateOidcReq = RPC.AuthOidcReq
|
|
32
17
|
|
|
33
18
|
/* Errors */
|
|
34
19
|
|
|
35
|
-
export type RegistrationErrors = NotSupported | BadRequest |
|
|
20
|
+
export type RegistrationErrors = NotSupported | BadRequest | RPC.RegisterOidcErrors
|
|
36
21
|
|
|
37
|
-
export type AuthenticationErrors = NotSupported | BadRequest |
|
|
22
|
+
export type AuthenticationErrors = NotSupported | BadRequest | RPC.AuthOidcErrors
|
|
38
23
|
|
|
39
24
|
/* Service */
|
|
40
25
|
|
|
41
|
-
export
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
)
|
|
26
|
+
export class SocialService extends Context.Tag('@services/SocialService')<
|
|
27
|
+
SocialService,
|
|
28
|
+
{
|
|
29
|
+
registerOidc: (req: RegisterOidcReq) => E.Effect<Principal, RegistrationErrors>
|
|
30
|
+
authenticateOidc: (req: AuthenticateOidcReq) => E.Effect<Principal, AuthenticationErrors>
|
|
31
|
+
}
|
|
32
|
+
>() {}
|
|
49
33
|
|
|
50
34
|
/* Effects */
|
|
51
35
|
|
|
@@ -58,16 +42,8 @@ export const registerOidc = (
|
|
|
58
42
|
yield* _(E.logInfo('Registering social account'))
|
|
59
43
|
|
|
60
44
|
const rpcClient = yield* _(SocialClient)
|
|
61
|
-
|
|
62
|
-
const
|
|
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
|
-
)
|
|
45
|
+
const rpcRequest = new RPC.RegisterOidcReq(request)
|
|
46
|
+
const { principal } = yield* _(rpcClient.registerOidc(rpcRequest))
|
|
71
47
|
|
|
72
48
|
return principal
|
|
73
49
|
})
|
|
@@ -80,11 +56,8 @@ export const authenticateOidc = (
|
|
|
80
56
|
yield* _(E.logInfo('Authenticating with social account'))
|
|
81
57
|
|
|
82
58
|
const rpcClient = yield* _(SocialClient)
|
|
83
|
-
const rpcRequest = new
|
|
84
|
-
|
|
85
|
-
const { principal } = yield* _(
|
|
86
|
-
rpcClient.authenticateOidc(rpcRequest)
|
|
87
|
-
)
|
|
59
|
+
const rpcRequest = new RPC.AuthOidcReq(request)
|
|
60
|
+
const { principal } = yield* _(rpcClient.authenticateOidc(rpcRequest))
|
|
88
61
|
|
|
89
62
|
return principal
|
|
90
63
|
})
|
|
@@ -100,7 +73,7 @@ export const SocialServiceLive = Layer.effect(
|
|
|
100
73
|
|
|
101
74
|
return SocialService.of({
|
|
102
75
|
registerOidc: flow(registerOidc, E.provide(context)),
|
|
103
|
-
authenticateOidc: flow(authenticateOidc, E.provide(context))
|
|
76
|
+
authenticateOidc: flow(authenticateOidc, E.provide(context)),
|
|
104
77
|
})
|
|
105
78
|
}),
|
|
106
79
|
)
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { Effect as E, Layer, pipe } from 'effect'
|
|
2
2
|
import { mock } from 'vitest-mock-extended'
|
|
3
|
-
import {
|
|
3
|
+
import { BrowserStorage, StorageServiceLive } from './storage.js'
|
|
4
4
|
|
|
5
5
|
const storageTest = Layer.effect(
|
|
6
|
-
|
|
6
|
+
BrowserStorage,
|
|
7
7
|
E.sync(() => mock<Storage>()),
|
|
8
8
|
)
|
|
9
9
|
|
|
10
|
-
export const testLayers = (storage: Layer.Layer<
|
|
10
|
+
export const testLayers = (storage: Layer.Layer<BrowserStorage> = storageTest) => {
|
|
11
11
|
const storageService = pipe(StorageServiceLive, Layer.provide(storage))
|
|
12
|
-
|
|
13
12
|
return Layer.merge(storage, storageService)
|
|
14
13
|
}
|
|
15
14
|
|
|
@@ -2,7 +2,13 @@ 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
|
import { principal, testLayers } from './storage.fixture.js'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
BrowserStorage,
|
|
7
|
+
StorageService,
|
|
8
|
+
clearExpiredToken,
|
|
9
|
+
clearToken,
|
|
10
|
+
getToken,
|
|
11
|
+
} from './storage.js'
|
|
6
12
|
|
|
7
13
|
// eslint chokes on expect(storage.setItem) etc
|
|
8
14
|
/* eslint @typescript-eslint/unbound-method: 0 */
|
|
@@ -13,7 +19,7 @@ describe('storeToken should', () => {
|
|
|
13
19
|
const service = yield* _(StorageService)
|
|
14
20
|
yield* _(service.storeToken(principal))
|
|
15
21
|
|
|
16
|
-
const storage = yield* _(
|
|
22
|
+
const storage = yield* _(BrowserStorage)
|
|
17
23
|
expect(storage.setItem).toHaveBeenCalled()
|
|
18
24
|
})
|
|
19
25
|
|
|
@@ -30,7 +36,7 @@ describe('storeToken should', () => {
|
|
|
30
36
|
const service = yield* _(StorageService)
|
|
31
37
|
yield* _(service.storeToken(principal))
|
|
32
38
|
|
|
33
|
-
const storage = yield* _(
|
|
39
|
+
const storage = yield* _(BrowserStorage)
|
|
34
40
|
expect(storage.setItem).toHaveBeenCalledWith('passlock:passkey:token', expect.any(String))
|
|
35
41
|
})
|
|
36
42
|
|
|
@@ -47,10 +53,13 @@ describe('storeToken should', () => {
|
|
|
47
53
|
const service = yield* _(StorageService)
|
|
48
54
|
yield* _(service.storeToken(principal))
|
|
49
55
|
|
|
50
|
-
const storage = yield* _(
|
|
51
|
-
const token = principal.
|
|
52
|
-
const expiry = principal.
|
|
53
|
-
expect(storage.setItem).toHaveBeenCalledWith(
|
|
56
|
+
const storage = yield* _(BrowserStorage)
|
|
57
|
+
const token = principal.jti
|
|
58
|
+
const expiry = principal.exp.getTime()
|
|
59
|
+
expect(storage.setItem).toHaveBeenCalledWith(
|
|
60
|
+
'passlock:passkey:token',
|
|
61
|
+
`${token}:${expiry.toFixed(0)}`,
|
|
62
|
+
)
|
|
54
63
|
})
|
|
55
64
|
|
|
56
65
|
const effect = pipe(
|
|
@@ -68,17 +77,17 @@ describe('getToken should', () => {
|
|
|
68
77
|
const service = yield* _(StorageService)
|
|
69
78
|
yield* _(service.getToken('passkey'))
|
|
70
79
|
|
|
71
|
-
const storage = yield* _(
|
|
80
|
+
const storage = yield* _(BrowserStorage)
|
|
72
81
|
expect(storage.getItem).toHaveBeenCalled()
|
|
73
82
|
expect(storage.getItem).toHaveBeenCalledWith('passlock:passkey:token')
|
|
74
83
|
})
|
|
75
84
|
|
|
76
85
|
const storageTest = Layer.effect(
|
|
77
|
-
|
|
86
|
+
BrowserStorage,
|
|
78
87
|
E.sync(() => {
|
|
79
88
|
const mockStorage = mock<Storage>()
|
|
80
89
|
const expiry = Date.now() + 1000
|
|
81
|
-
mockStorage.getItem.mockReturnValue(`token:${expiry}`)
|
|
90
|
+
mockStorage.getItem.mockReturnValue(`token:${expiry.toFixed(0)}`)
|
|
82
91
|
return mockStorage
|
|
83
92
|
}),
|
|
84
93
|
)
|
|
@@ -106,11 +115,11 @@ describe('getToken should', () => {
|
|
|
106
115
|
)
|
|
107
116
|
|
|
108
117
|
const storageTest = Layer.effect(
|
|
109
|
-
|
|
118
|
+
BrowserStorage,
|
|
110
119
|
E.sync(() => {
|
|
111
120
|
const mockStorage = mock<Storage>()
|
|
112
121
|
const expiry = Date.now() - 1000
|
|
113
|
-
mockStorage.getItem.mockReturnValue(`token:${expiry}`)
|
|
122
|
+
mockStorage.getItem.mockReturnValue(`token:${expiry.toFixed(0)}`)
|
|
114
123
|
return mockStorage
|
|
115
124
|
}),
|
|
116
125
|
)
|
|
@@ -127,7 +136,7 @@ describe('getToken should', () => {
|
|
|
127
136
|
describe('clearToken should', () => {
|
|
128
137
|
test('clear the token in local storage', () => {
|
|
129
138
|
const assertions = E.gen(function* (_) {
|
|
130
|
-
const storage = yield* _(
|
|
139
|
+
const storage = yield* _(BrowserStorage)
|
|
131
140
|
yield* _(clearToken('passkey'))
|
|
132
141
|
expect(storage.removeItem).toHaveBeenCalledWith('passlock:passkey:token')
|
|
133
142
|
})
|
|
@@ -144,18 +153,18 @@ describe('clearToken should', () => {
|
|
|
144
153
|
describe('clearExpiredToken should', () => {
|
|
145
154
|
test('clear an expired token from local storage', () => {
|
|
146
155
|
const assertions = E.gen(function* (_) {
|
|
147
|
-
const storage = yield* _(
|
|
156
|
+
const storage = yield* _(BrowserStorage)
|
|
148
157
|
yield* _(clearExpiredToken('passkey'))
|
|
149
158
|
expect(storage.getItem).toHaveBeenCalledWith('passlock:passkey:token')
|
|
150
159
|
expect(storage.removeItem).toHaveBeenCalledWith('passlock:passkey:token')
|
|
151
160
|
})
|
|
152
161
|
|
|
153
162
|
const storageTest = Layer.effect(
|
|
154
|
-
|
|
163
|
+
BrowserStorage,
|
|
155
164
|
E.sync(() => {
|
|
156
165
|
const mockStorage = mock<Storage>()
|
|
157
166
|
const expiry = Date.now() - 1000
|
|
158
|
-
mockStorage.getItem.mockReturnValue(`token:${expiry}`)
|
|
167
|
+
mockStorage.getItem.mockReturnValue(`token:${expiry.toFixed(0)}`)
|
|
159
168
|
return mockStorage
|
|
160
169
|
}),
|
|
161
170
|
)
|
|
@@ -170,18 +179,18 @@ describe('clearExpiredToken should', () => {
|
|
|
170
179
|
|
|
171
180
|
test('leave a live token in local storage', () => {
|
|
172
181
|
const assertions = E.gen(function* (_) {
|
|
173
|
-
const storage = yield* _(
|
|
182
|
+
const storage = yield* _(BrowserStorage)
|
|
174
183
|
yield* _(clearExpiredToken('passkey'))
|
|
175
184
|
expect(storage.getItem).toHaveBeenCalledWith('passlock:passkey:token')
|
|
176
185
|
expect(storage.removeItem).not.toHaveBeenCalled()
|
|
177
186
|
})
|
|
178
187
|
|
|
179
188
|
const storageTest = Layer.effect(
|
|
180
|
-
|
|
189
|
+
BrowserStorage,
|
|
181
190
|
E.sync(() => {
|
|
182
191
|
const mockStorage = mock<Storage>()
|
|
183
192
|
const expiry = Date.now() + 1000
|
|
184
|
-
mockStorage.getItem.mockReturnValue(`token:${expiry}`)
|
|
193
|
+
mockStorage.getItem.mockReturnValue(`token:${expiry.toFixed(0)}`)
|
|
185
194
|
return mockStorage
|
|
186
195
|
}),
|
|
187
196
|
)
|
package/src/storage/storage.ts
CHANGED
|
@@ -13,33 +13,31 @@ export type AuthType = 'email' | 'passkey' | 'apple' | 'google'
|
|
|
13
13
|
export type StoredToken = {
|
|
14
14
|
token: string
|
|
15
15
|
authType: AuthType
|
|
16
|
-
|
|
16
|
+
expiry: number
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/* Service */
|
|
20
20
|
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
export const StorageService = Context.GenericTag<StorageService>('@services/StorageService')
|
|
21
|
+
export class StorageService extends Context.Tag('@services/StorageService')<
|
|
22
|
+
StorageService,
|
|
23
|
+
{
|
|
24
|
+
storeToken: (principal: Principal) => E.Effect<void>
|
|
25
|
+
getToken: (authType: AuthType) => E.Effect<StoredToken, NoSuchElementException>
|
|
26
|
+
clearToken: (authType: AuthType) => E.Effect<void>
|
|
27
|
+
clearExpiredToken: (authType: AuthType) => E.Effect<void>
|
|
28
|
+
clearExpiredTokens: E.Effect<void>
|
|
29
|
+
}
|
|
30
|
+
>() {}
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
export const Storage = Context.GenericTag<Storage>('@services/Storage')
|
|
32
|
+
export class BrowserStorage extends Context.Tag('@services/Storage')<BrowserStorage, Storage>() {}
|
|
35
33
|
|
|
36
34
|
export const buildKey = (authType: AuthType) => `passlock:${authType}:token`
|
|
37
35
|
|
|
38
36
|
// principal => token:expireAt
|
|
39
37
|
export const compressToken = (principal: Principal): string => {
|
|
40
|
-
const expireAt = principal.
|
|
41
|
-
const token = principal.
|
|
42
|
-
return `${token}:${expireAt}`
|
|
38
|
+
const expireAt = principal.exp.getTime()
|
|
39
|
+
const token = principal.jti
|
|
40
|
+
return `${token}:${expireAt.toFixed(0)}`
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
// token:expireAt => { authType, token, expireAt }
|
|
@@ -48,12 +46,14 @@ export const expandToken =
|
|
|
48
46
|
(s: string): O.Option<StoredToken> => {
|
|
49
47
|
const tokens = s.split(':')
|
|
50
48
|
if (tokens.length !== 2) return O.none()
|
|
49
|
+
|
|
50
|
+
const [token, expireAtString] = tokens
|
|
51
|
+
if (token === undefined || expireAtString === undefined) return O.none()
|
|
51
52
|
|
|
52
|
-
const [token, expireAtString] = tokens
|
|
53
53
|
const parse = O.liftThrowable(Number.parseInt)
|
|
54
54
|
const expireAt = parse(expireAtString)
|
|
55
55
|
|
|
56
|
-
return O.map(expireAt,
|
|
56
|
+
return O.map(expireAt, expiry => ({ authType, token, expiry }))
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/* Effects */
|
|
@@ -63,13 +63,13 @@ export const expandToken =
|
|
|
63
63
|
* @param principal
|
|
64
64
|
* @returns
|
|
65
65
|
*/
|
|
66
|
-
export const storeToken = (principal: Principal): E.Effect<void, never,
|
|
66
|
+
export const storeToken = (principal: Principal): E.Effect<void, never, BrowserStorage> => {
|
|
67
67
|
return E.gen(function* (_) {
|
|
68
|
-
const localStorage = yield* _(
|
|
68
|
+
const localStorage = yield* _(BrowserStorage)
|
|
69
69
|
|
|
70
70
|
const storeEffect = E.try(() => {
|
|
71
71
|
const compressed = compressToken(principal)
|
|
72
|
-
const key = buildKey(principal.
|
|
72
|
+
const key = buildKey(principal.authType)
|
|
73
73
|
localStorage.setItem(key, compressed)
|
|
74
74
|
}).pipe(E.orElse(() => E.void)) // We dont care if it fails
|
|
75
75
|
|
|
@@ -79,20 +79,20 @@ export const storeToken = (principal: Principal): E.Effect<void, never, Storage>
|
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
81
|
* Get stored token from local storage
|
|
82
|
-
* @param
|
|
82
|
+
* @param authenticator
|
|
83
83
|
* @returns
|
|
84
84
|
*/
|
|
85
85
|
export const getToken = (
|
|
86
|
-
|
|
87
|
-
): E.Effect<StoredToken, NoSuchElementException,
|
|
86
|
+
authenticator: AuthType,
|
|
87
|
+
): E.Effect<StoredToken, NoSuchElementException, BrowserStorage> => {
|
|
88
88
|
return E.gen(function* (_) {
|
|
89
|
-
const localStorage = yield* _(
|
|
89
|
+
const localStorage = yield* _(BrowserStorage)
|
|
90
90
|
|
|
91
91
|
const getEffect = pipe(
|
|
92
|
-
O.some(buildKey(
|
|
92
|
+
O.some(buildKey(authenticator)),
|
|
93
93
|
O.flatMap(key => pipe(localStorage.getItem(key), O.fromNullable)),
|
|
94
|
-
O.flatMap(expandToken(
|
|
95
|
-
O.filter(({
|
|
94
|
+
O.flatMap(expandToken(authenticator)),
|
|
95
|
+
O.filter(({ expiry }) => expiry > Date.now()),
|
|
96
96
|
)
|
|
97
97
|
|
|
98
98
|
return yield* _(getEffect)
|
|
@@ -104,9 +104,9 @@ export const getToken = (
|
|
|
104
104
|
* @param authType
|
|
105
105
|
* @returns
|
|
106
106
|
*/
|
|
107
|
-
export const clearToken = (authType: AuthType): E.Effect<void, never,
|
|
107
|
+
export const clearToken = (authType: AuthType): E.Effect<void, never, BrowserStorage> => {
|
|
108
108
|
return E.gen(function* (_) {
|
|
109
|
-
const localStorage = yield* _(
|
|
109
|
+
const localStorage = yield* _(BrowserStorage)
|
|
110
110
|
localStorage.removeItem(buildKey(authType))
|
|
111
111
|
})
|
|
112
112
|
}
|
|
@@ -117,15 +117,15 @@ export const clearToken = (authType: AuthType): E.Effect<void, never, Storage> =
|
|
|
117
117
|
* @param defer
|
|
118
118
|
* @returns
|
|
119
119
|
*/
|
|
120
|
-
export const clearExpiredToken = (authType: AuthType): E.Effect<void, never,
|
|
120
|
+
export const clearExpiredToken = (authType: AuthType): E.Effect<void, never, BrowserStorage> => {
|
|
121
121
|
const key = buildKey(authType)
|
|
122
122
|
|
|
123
123
|
const effect = E.gen(function* (_) {
|
|
124
|
-
const storage = yield* _(
|
|
124
|
+
const storage = yield* _(BrowserStorage)
|
|
125
125
|
const item = yield* _(O.fromNullable(storage.getItem(key)))
|
|
126
126
|
const token = yield* _(expandToken(authType)(item))
|
|
127
127
|
|
|
128
|
-
if (token.
|
|
128
|
+
if (token.expiry < Date.now()) {
|
|
129
129
|
storage.removeItem(key)
|
|
130
130
|
}
|
|
131
131
|
})
|
|
@@ -140,7 +140,7 @@ export const clearExpiredToken = (authType: AuthType): E.Effect<void, never, Sto
|
|
|
140
140
|
)
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
export const clearExpiredTokens: E.Effect<void, never,
|
|
143
|
+
export const clearExpiredTokens: E.Effect<void, never, BrowserStorage> = E.all([
|
|
144
144
|
clearExpiredToken('passkey'),
|
|
145
145
|
clearExpiredToken('email'),
|
|
146
146
|
clearExpiredToken('google'),
|
|
@@ -153,7 +153,7 @@ export const clearExpiredTokens: E.Effect<void, never, Storage> = E.all([
|
|
|
153
153
|
export const StorageServiceLive = Layer.effect(
|
|
154
154
|
StorageService,
|
|
155
155
|
E.gen(function* (_) {
|
|
156
|
-
const context = yield* _(E.context<
|
|
156
|
+
const context = yield* _(E.context<BrowserStorage>())
|
|
157
157
|
|
|
158
158
|
return {
|
|
159
159
|
storeToken: flow(storeToken, E.provide(context)),
|
package/src/test/fixtures.ts
CHANGED
|
@@ -8,16 +8,31 @@ export const session = 'session'
|
|
|
8
8
|
export const token = 'token'
|
|
9
9
|
export const code = 'code'
|
|
10
10
|
export const authType = 'passkey'
|
|
11
|
-
export const
|
|
11
|
+
export const expiry = Date.now() + 10000
|
|
12
12
|
|
|
13
13
|
export const principal: Principal = {
|
|
14
|
+
jti: 'token',
|
|
14
15
|
token: 'token',
|
|
16
|
+
sub: 'user-1',
|
|
17
|
+
iss: 'idp.passlock.dev',
|
|
18
|
+
aud: 'tenancy_id',
|
|
19
|
+
iat: new Date(),
|
|
20
|
+
nbf: new Date(),
|
|
21
|
+
exp: new Date(Date.now() + 5 * 60 * 1000),
|
|
22
|
+
email: 'john.doe@gmail.com',
|
|
23
|
+
givenName: 'john',
|
|
24
|
+
familyName: 'doe',
|
|
25
|
+
emailVerified: false,
|
|
26
|
+
authType: 'passkey',
|
|
27
|
+
authId: 'auth-1',
|
|
28
|
+
userVerified: true,
|
|
29
|
+
// legacy
|
|
15
30
|
user: {
|
|
16
|
-
id: '1',
|
|
17
|
-
email: 'john.doe@gmail.com',
|
|
31
|
+
id: 'user-1',
|
|
18
32
|
givenName: 'john',
|
|
19
33
|
familyName: 'doe',
|
|
20
|
-
|
|
34
|
+
email: 'john.doe@gmail.com',
|
|
35
|
+
emailVerified: false
|
|
21
36
|
},
|
|
22
37
|
authStatement: {
|
|
23
38
|
authType: 'passkey',
|
|
@@ -37,7 +52,7 @@ export const capabilitiesTest = L.succeed(
|
|
|
37
52
|
}),
|
|
38
53
|
)
|
|
39
54
|
|
|
40
|
-
export const storedToken: StoredToken = { token, authType,
|
|
55
|
+
export const storedToken: StoredToken = { token, authType, expiry }
|
|
41
56
|
|
|
42
57
|
export const storageServiceTest = L.succeed(
|
|
43
58
|
StorageService,
|
|
@@ -50,4 +65,4 @@ export const storageServiceTest = L.succeed(
|
|
|
50
65
|
}),
|
|
51
66
|
)
|
|
52
67
|
|
|
53
|
-
export const notImplemented = new BadRequest({ message: 'Not implemented' })
|
|
68
|
+
export const notImplemented = new BadRequest({ message: 'Not implemented' })
|
package/src/user/user.fixture.ts
CHANGED
|
@@ -1,21 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
IsExistingUserReq,
|
|
3
|
+
IsExistingUserRes,
|
|
4
|
+
ResendEmailReq,
|
|
5
|
+
ResendEmailRes,
|
|
6
|
+
VerifyEmailRes,
|
|
7
|
+
} from '@passlock/shared/dist/rpc/user.js'
|
|
8
|
+
import { Effect as E, Layer as L, Option as O } from 'effect'
|
|
9
|
+
import { UserClient } from '../rpc/user.js'
|
|
3
10
|
import * as Fixtures from '../test/fixtures.js'
|
|
4
11
|
import type { ResendEmail } from './user.js'
|
|
5
12
|
|
|
6
13
|
export const email = 'jdoe@gmail.com'
|
|
7
14
|
export const isRegisteredReq = new IsExistingUserReq({ email })
|
|
8
|
-
export const isRegisteredRes = new IsExistingUserRes({ existingUser: false })
|
|
15
|
+
export const isRegisteredRes = new IsExistingUserRes({ existingUser: false, detail: O.none() })
|
|
9
16
|
export const verifyEmailRes = new VerifyEmailRes({ principal: Fixtures.principal })
|
|
10
17
|
export const resendEmailReq: ResendEmail = { userId: '123', method: 'code' }
|
|
11
|
-
export const rpcResendEmailReq = new ResendEmailReq({
|
|
12
|
-
|
|
18
|
+
export const rpcResendEmailReq = new ResendEmailReq({
|
|
19
|
+
userId: '123',
|
|
20
|
+
verifyEmail: { method: 'code' },
|
|
21
|
+
})
|
|
22
|
+
export const rpcResendEmailRes = new ResendEmailRes({})
|
|
13
23
|
|
|
14
24
|
export const rpcClientTest = L.succeed(
|
|
15
25
|
UserClient,
|
|
16
26
|
UserClient.of({
|
|
17
|
-
isExistingUser: () => E.succeed({ existingUser: true }),
|
|
27
|
+
isExistingUser: () => E.succeed({ existingUser: true, detail: O.none() }),
|
|
18
28
|
verifyEmail: () => E.succeed(verifyEmailRes),
|
|
19
29
|
resendVerificationEmail: () => E.fail(Fixtures.notImplemented),
|
|
20
30
|
}),
|
|
21
|
-
)
|
|
31
|
+
)
|
package/src/user/user.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
|
+
import { UserClient } from '../rpc/user.js'
|
|
5
5
|
import * as Fixture from './user.fixture.js'
|
|
6
6
|
import { UserService, UserServiceLive } from './user.js'
|
|
7
7
|
|
|
@@ -76,10 +76,7 @@ describe('resendVerificationEmail should', () => {
|
|
|
76
76
|
|
|
77
77
|
const layers = L.merge(service, rpcClientTest)
|
|
78
78
|
|
|
79
|
-
const effect = pipe(
|
|
80
|
-
E.provide(assertions, layers),
|
|
81
|
-
Logger.withMinimumLogLevel(LogLevel.None)
|
|
82
|
-
)
|
|
79
|
+
const effect = pipe(E.provide(assertions, layers), Logger.withMinimumLogLevel(LogLevel.None))
|
|
83
80
|
|
|
84
81
|
return E.runPromise(effect)
|
|
85
82
|
})
|
package/src/user/user.ts
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* Check for an existing user
|
|
3
3
|
*/
|
|
4
4
|
import type { BadRequest, Disabled, NotFound } from '@passlock/shared/dist/error/error.js'
|
|
5
|
-
import { IsExistingUserReq, ResendEmailReq
|
|
5
|
+
import { IsExistingUserReq, ResendEmailReq } from '@passlock/shared/dist/rpc/user.js'
|
|
6
6
|
import type { VerifyEmail } from '@passlock/shared/dist/schema/email.js'
|
|
7
7
|
import { Context, Effect as E, Layer, flow } from 'effect'
|
|
8
|
+
import { UserClient } from '../rpc/user.js'
|
|
8
9
|
|
|
9
10
|
/* Requests */
|
|
10
11
|
|
|
@@ -17,12 +18,13 @@ export type ResendEmailErrors = BadRequest | NotFound | Disabled
|
|
|
17
18
|
|
|
18
19
|
/* Service */
|
|
19
20
|
|
|
20
|
-
export
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
export class UserService extends Context.Tag('@services/UserService')<
|
|
22
|
+
UserService,
|
|
23
|
+
{
|
|
24
|
+
isExistingUser: (request: Email) => E.Effect<boolean, BadRequest>
|
|
25
|
+
resendVerificationEmail: (request: ResendEmail) => E.Effect<void, ResendEmailErrors>
|
|
26
|
+
}
|
|
27
|
+
>() {}
|
|
26
28
|
|
|
27
29
|
/* Effects */
|
|
28
30
|
|
|
@@ -40,7 +42,9 @@ export const isExistingUser = (request: Email): E.Effect<boolean, BadRequest, De
|
|
|
40
42
|
})
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
export const resendVerificationEmail = (
|
|
45
|
+
export const resendVerificationEmail = (
|
|
46
|
+
request: ResendEmail,
|
|
47
|
+
): E.Effect<void, ResendEmailErrors, Dependencies> => {
|
|
44
48
|
return E.gen(function* (_) {
|
|
45
49
|
yield* _(E.logInfo('Resending verification email'))
|
|
46
50
|
const rpcClient = yield* _(UserClient)
|
|
@@ -60,7 +64,7 @@ export const UserServiceLive = Layer.effect(
|
|
|
60
64
|
const context = yield* _(E.context<UserClient>())
|
|
61
65
|
return UserService.of({
|
|
62
66
|
isExistingUser: flow(isExistingUser, E.provide(context)),
|
|
63
|
-
resendVerificationEmail: flow(resendVerificationEmail, E.provide(context))
|
|
67
|
+
resendVerificationEmail: flow(resendVerificationEmail, E.provide(context)),
|
|
64
68
|
})
|
|
65
69
|
}),
|
|
66
70
|
)
|
package/src/version.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PASSLOCK_CLIENT_VERSION = '#{LATEST}#'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"authenticate.d.ts","sourceRoot":"","sources":["../../src/authentication/authenticate.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,oBAAoB,EACpB,KAAK,YAAY,EAClB,MAAM,sCAAsC,CAAA;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAA;AACpG,OAAO,EAAE,oBAAoB,EAA+B,MAAM,6CAA6C,CAAA;AAC/G,OAAO,KAAK,EACV,wBAAwB,EACxB,gBAAgB,EACjB,MAAM,yCAAyC,CAAA;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,2CAA2C,CAAA;AACrE,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC,EAAE,KAAK,EAAc,MAAM,QAAQ,CAAA;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAA;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAItD,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;CACpC,CAAA;AAID,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG,aAAa,GAAG,kBAAkB,CAAA;AAIpF,MAAM,MAAM,aAAa,GAAG,CAC1B,OAAO,EAAE,wBAAwB,KAC9B,CAAC,CAAC,MAAM,CAAC,wBAAwB,EAAE,oBAAoB,CAAC,CAAA;AAE7D,eAAO,MAAM,aAAa,2CAAqD,CAAA;AAI/E,MAAM,MAAM,qBAAqB,GAAG;IAClC,mBAAmB,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAA;CACnG,CAAA;AAED,eAAO,MAAM,qBAAqB,2DAEjC,CAAA;AA4CD,KAAK,YAAY,GAAG,aAAa,GAAG,YAAY,GAAG,cAAc,GAAG,oBAAoB,CAAA;AAExF,eAAO,MAAM,mBAAmB,YACrB,qBAAqB,KAC7B,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,oBAAoB,EAAE,YAAY,CAgCxD,CAAA;AAKD,eAAO,MAAM,uBAAuB,iHASnC,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"authenticate.fixture.d.ts","sourceRoot":"","sources":["../../src/authentication/authenticate.fixture.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,UAAU,EACV,eAAe,EACf,eAAe,EAChB,MAAM,6CAA6C,CAAA;AACpD,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAA;AACrF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yCAAyC,CAAA;AACvF,OAAO,EAAe,KAAK,IAAI,CAAC,EAAE,MAAM,QAAQ,CAAA;AAEhD,OAAO,EAAE,aAAa,EAAE,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AAE7E,eAAO,MAAM,OAAO,YAAY,CAAA;AAChC,eAAO,MAAM,KAAK,UAAU,CAAA;AAC5B,eAAO,MAAM,IAAI,SAAS,CAAA;AAC1B,eAAO,MAAM,QAAQ,YAAY,CAAA;AACjC,eAAO,MAAM,QAAQ,QAAqB,CAAA;AAE1C,eAAO,MAAM,OAAO,EAAE,qBAErB,CAAA;AAED,eAAO,MAAM,aAAa,YAQxB,CAAA;AAEF,eAAO,MAAM,UAAU,EAAE,wBAYxB,CAAA;AAED,eAAO,MAAM,kBAAkB,iBAA+C,CAAA;AAE9E,eAAO,MAAM,kBAAkB,iBAAyD,CAAA;AAExF,eAAO,MAAM,oBAAoB,mBAAgD,CAAA;AAEjF,eAAO,MAAM,iBAAiB,gBAAwD,CAAA;AAEtF,eAAO,MAAM,iBAAiB,sCAG7B,CAAA;AAED,eAAO,MAAM,aAAa,6CAMzB,CAAA;AAED,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;CAAqB,CAAA;AAC3C,eAAO,MAAM,gBAAgB,+EAA4B,CAAA;AACzD,eAAO,MAAM,kBAAkB,uEAA8B,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"authenticate.test.d.ts","sourceRoot":"","sources":["../../src/authentication/authenticate.test.ts"],"names":[],"mappings":""}
|