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