@passlock/client 0.9.22 → 0.9.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -5
- package/README.template.md +130 -0
- package/dist/authentication/authenticate.d.ts +16 -16
- package/dist/authentication/authenticate.fixture.d.ts +21 -7
- 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 +9 -5
- package/dist/capabilities/capabilities.js +11 -2
- 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 +23 -46
- package/dist/effect.js +55 -51
- package/dist/effect.js.map +1 -1
- package/dist/email/email.d.ts +42 -12
- package/dist/email/email.fixture.d.ts +19 -5
- package/dist/email/email.fixture.js +5 -4
- package/dist/email/email.fixture.js.map +1 -1
- package/dist/email/email.js +44 -8
- package/dist/email/email.js.map +1 -1
- package/dist/event/event.d.ts +4 -2
- package/dist/event/event.js +4 -1
- 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 +14 -1
- package/dist/logging/eventLogger.js.map +1 -1
- package/dist/registration/register.d.ts +19 -22
- package/dist/registration/register.fixture.d.ts +20 -6
- 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 +1 -2
- package/dist/rpc/authentication.js +2 -1
- package/dist/rpc/authentication.js.map +1 -1
- package/dist/rpc/client.d.ts +5 -2
- package/dist/rpc/client.js +12 -3
- package/dist/rpc/client.js.map +1 -1
- package/dist/rpc/config.d.ts +0 -1
- package/dist/rpc/connection.d.ts +1 -2
- package/dist/rpc/connection.js +2 -1
- package/dist/rpc/connection.js.map +1 -1
- package/dist/rpc/registration.d.ts +1 -2
- package/dist/rpc/registration.js +2 -1
- package/dist/rpc/registration.js.map +1 -1
- package/dist/rpc/social.d.ts +1 -2
- package/dist/rpc/social.js +2 -1
- package/dist/rpc/social.js.map +1 -1
- package/dist/rpc/user.d.ts +1 -2
- package/dist/rpc/user.js +2 -1
- package/dist/rpc/user.js.map +1 -1
- package/dist/social/social.d.ts +17 -24
- package/dist/social/social.fixture.d.ts +22 -10
- package/dist/social/social.fixture.js +8 -14
- package/dist/social/social.fixture.js.map +1 -1
- package/dist/social/social.js +15 -11
- package/dist/social/social.js.map +1 -1
- package/dist/storage/storage.d.ts +41 -13
- 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 +52 -15
- package/dist/storage/storage.js.map +1 -1
- package/dist/test/fixtures.d.ts +2 -3
- package/dist/test/fixtures.js +20 -5
- package/dist/test/fixtures.js.map +1 -1
- package/dist/user/user.d.ts +9 -6
- 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 +12 -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 +38 -30
- package/src/authentication/authenticate.fixture.ts +10 -7
- package/src/authentication/authenticate.test.ts +61 -18
- package/src/authentication/authenticate.ts +37 -33
- package/src/capabilities/capabilities.ts +11 -9
- package/src/connection/connection.fixture.ts +4 -1
- package/src/connection/connection.test.ts +4 -3
- package/src/connection/connection.ts +10 -8
- package/src/effect.ts +129 -134
- package/src/email/email.fixture.ts +7 -4
- package/src/email/email.test.ts +6 -5
- package/src/email/email.ts +27 -17
- package/src/event/event.node.test.ts +1 -0
- package/src/event/event.test.ts +1 -0
- package/src/event/event.ts +2 -1
- package/src/index.ts +235 -173
- package/src/logging/eventLogger.test.ts +2 -1
- package/src/logging/eventLogger.ts +3 -3
- package/src/registration/register.fixture.ts +16 -8
- package/src/registration/register.test.ts +16 -10
- package/src/registration/register.ts +37 -35
- package/src/rpc/authentication.ts +43 -0
- package/src/rpc/client.ts +174 -0
- package/src/rpc/config.ts +18 -0
- package/src/rpc/connection.ts +30 -0
- package/src/rpc/registration.ts +41 -0
- package/src/rpc/social.ts +45 -0
- package/src/rpc/user.ts +57 -0
- package/src/social/social.fixture.ts +12 -18
- package/src/social/social.test.ts +16 -30
- package/src/social/social.ts +22 -47
- package/src/storage/storage.fixture.ts +4 -4
- package/src/storage/storage.test.ts +29 -19
- package/src/storage/storage.ts +37 -36
- package/src/test/fixtures.ts +23 -6
- package/src/user/user.fixture.ts +19 -7
- package/src/user/user.test.ts +3 -5
- package/src/user/user.ts +16 -10
- 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/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/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/event/event.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/logging/eventLogger.d.ts.map +0 -1
- package/dist/registration/register.d.ts.map +0 -1
- package/dist/registration/register.fixture.d.ts.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/storage/storage.d.ts.map +0 -1
- package/dist/storage/storage.fixture.d.ts.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/version.d.ts.map +0 -1
- package/src/config.ts +0 -42
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { Duplicate, InternalBrowserError } from '@passlock/shared/dist/error/error.js'
|
|
2
|
-
import { RegistrationClient } from '@passlock/shared/dist/rpc/registration.js'
|
|
3
1
|
import { Effect as E, Layer as L, Layer, LogLevel, Logger, pipe } from 'effect'
|
|
4
2
|
import { describe, expect, test, vi } from 'vitest'
|
|
5
3
|
import { mock } from 'vitest-mock-extended'
|
|
4
|
+
|
|
5
|
+
import { Duplicate, InternalBrowserError } from '@passlock/shared/dist/error/error.js'
|
|
6
|
+
|
|
6
7
|
import * as Fixture from './register.fixture.js'
|
|
8
|
+
import { RegistrationClient } from '../rpc/registration.js'
|
|
7
9
|
import { CreateCredential, RegistrationService, RegistrationServiceLive } from './register.js'
|
|
8
10
|
|
|
9
11
|
describe('register should', () => {
|
|
@@ -70,7 +72,9 @@ describe('register should', () => {
|
|
|
70
72
|
yield* _(service.registerPasskey(Fixture.registrationRequest))
|
|
71
73
|
|
|
72
74
|
const rpcClient = yield* _(RegistrationClient)
|
|
73
|
-
expect(rpcClient.verifyRegistrationCredential).toHaveBeenCalledWith(
|
|
75
|
+
expect(rpcClient.verifyRegistrationCredential).toHaveBeenCalledWith(
|
|
76
|
+
Fixture.rpcVerificationReq,
|
|
77
|
+
)
|
|
74
78
|
})
|
|
75
79
|
|
|
76
80
|
const rpcClientTest = L.effect(
|
|
@@ -114,7 +118,9 @@ describe('register should', () => {
|
|
|
114
118
|
E.sync(() => {
|
|
115
119
|
const rpcMock = mock<RegistrationClient['Type']>()
|
|
116
120
|
|
|
117
|
-
rpcMock.getRegistrationOptions.mockReturnValue(
|
|
121
|
+
rpcMock.getRegistrationOptions.mockReturnValue(
|
|
122
|
+
E.fail(new Duplicate({ message: 'User already exists' })),
|
|
123
|
+
)
|
|
118
124
|
|
|
119
125
|
return rpcMock
|
|
120
126
|
}),
|
|
@@ -147,11 +153,11 @@ describe('register should', () => {
|
|
|
147
153
|
const createTest = L.effect(
|
|
148
154
|
CreateCredential,
|
|
149
155
|
E.sync(() => {
|
|
150
|
-
const
|
|
156
|
+
const createCredential = vi.fn()
|
|
151
157
|
|
|
152
|
-
|
|
158
|
+
createCredential.mockReturnValue(E.fail(new Duplicate({ message: 'boom!' })))
|
|
153
159
|
|
|
154
|
-
return
|
|
160
|
+
return { createCredential }
|
|
155
161
|
}),
|
|
156
162
|
)
|
|
157
163
|
|
|
@@ -185,11 +191,11 @@ describe('register should', () => {
|
|
|
185
191
|
const createTest = L.effect(
|
|
186
192
|
CreateCredential,
|
|
187
193
|
E.sync(() => {
|
|
188
|
-
const
|
|
194
|
+
const createCredential = vi.fn()
|
|
189
195
|
|
|
190
|
-
|
|
196
|
+
createCredential.mockReturnValue(E.fail(new InternalBrowserError({ message: 'boom!' })))
|
|
191
197
|
|
|
192
|
-
return
|
|
198
|
+
return { createCredential }
|
|
193
199
|
}),
|
|
194
200
|
)
|
|
195
201
|
|
|
@@ -2,41 +2,37 @@
|
|
|
2
2
|
* User & passkey registration effects
|
|
3
3
|
*/
|
|
4
4
|
import {
|
|
5
|
-
parseCreationOptionsFromJSON,
|
|
6
5
|
type CredentialCreationOptionsJSON,
|
|
6
|
+
parseCreationOptionsFromJSON,
|
|
7
7
|
} from '@github/webauthn-json/browser-ponyfill'
|
|
8
|
-
import type { NotSupported } from '@passlock/shared/dist/error/error.js'
|
|
9
|
-
import { Duplicate, InternalBrowserError } from '@passlock/shared/dist/error/error.js'
|
|
10
|
-
import type { OptionsErrors, VerificationErrors } from '@passlock/shared/dist/rpc/registration.js'
|
|
11
|
-
import { OptionsReq, RegistrationClient, VerificationReq } from '@passlock/shared/dist/rpc/registration.js'
|
|
12
|
-
import { VerifyEmail } from '@passlock/shared/dist/schema/email.js'
|
|
13
|
-
import type {
|
|
14
|
-
RegistrationCredential,
|
|
15
|
-
UserVerification,
|
|
16
|
-
} from '@passlock/shared/dist/schema/passkey.js'
|
|
17
|
-
import { Principal } from '@passlock/shared/dist/schema/principal.js'
|
|
18
8
|
import { Context, Effect as E, Layer, flow, pipe } from 'effect'
|
|
9
|
+
|
|
10
|
+
import type { Duplicate, NotSupported } from '@passlock/shared/dist/error/error.js'
|
|
11
|
+
import { InternalBrowserError } from '@passlock/shared/dist/error/error.js'
|
|
12
|
+
import type { OptionsErrors, VerificationErrors } from '@passlock/shared/dist/rpc/registration.js'
|
|
13
|
+
import { OptionsReq, VerificationReq } from '@passlock/shared/dist/rpc/registration.js'
|
|
14
|
+
import type { RegistrationCredential } from '@passlock/shared/dist/schema/passkey.js'
|
|
15
|
+
import type { Principal } from '@passlock/shared/dist/schema/principal.js'
|
|
16
|
+
|
|
19
17
|
import { Capabilities } from '../capabilities/capabilities.js'
|
|
18
|
+
import { RegistrationClient } from '../rpc/registration.js'
|
|
20
19
|
import { StorageService } from '../storage/storage.js'
|
|
21
|
-
import { UserService } from '../user/user.js'
|
|
20
|
+
import type { UserService } from '../user/user.js'
|
|
22
21
|
|
|
23
22
|
/* Requests */
|
|
24
23
|
|
|
25
|
-
export type RegistrationRequest =
|
|
26
|
-
email: string
|
|
27
|
-
givenName: string
|
|
28
|
-
familyName: string
|
|
29
|
-
userVerification?: UserVerification
|
|
30
|
-
verifyEmail?: VerifyEmail
|
|
31
|
-
}
|
|
24
|
+
export type RegistrationRequest = OptionsReq
|
|
32
25
|
|
|
33
26
|
/* Dependencies */
|
|
34
27
|
|
|
35
|
-
export
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
28
|
+
export class CreateCredential extends Context.Tag('@services/CreateCredential')<
|
|
29
|
+
CreateCredential,
|
|
30
|
+
{
|
|
31
|
+
createCredential: (
|
|
32
|
+
request: CredentialCreationOptions,
|
|
33
|
+
) => E.Effect<RegistrationCredential, InternalBrowserError | Duplicate>
|
|
34
|
+
}
|
|
35
|
+
>() {}
|
|
40
36
|
|
|
41
37
|
/* Errors */
|
|
42
38
|
|
|
@@ -44,13 +40,12 @@ export type RegistrationErrors = NotSupported | OptionsErrors | VerificationErro
|
|
|
44
40
|
|
|
45
41
|
/* Service */
|
|
46
42
|
|
|
47
|
-
export
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
)
|
|
43
|
+
export class RegistrationService extends Context.Tag('@services/RegistrationService')<
|
|
44
|
+
RegistrationService,
|
|
45
|
+
{
|
|
46
|
+
registerPasskey: (request: RegistrationRequest) => E.Effect<Principal, RegistrationErrors>
|
|
47
|
+
}
|
|
48
|
+
>() {}
|
|
54
49
|
|
|
55
50
|
/* Utilities */
|
|
56
51
|
|
|
@@ -94,7 +89,12 @@ const verifyCredential = (request: VerificationReq) => {
|
|
|
94
89
|
|
|
95
90
|
/* Effects */
|
|
96
91
|
|
|
97
|
-
type Dependencies =
|
|
92
|
+
type Dependencies =
|
|
93
|
+
| Capabilities
|
|
94
|
+
| CreateCredential
|
|
95
|
+
| StorageService
|
|
96
|
+
| UserService
|
|
97
|
+
| RegistrationClient
|
|
98
98
|
|
|
99
99
|
export const registerPasskey = (
|
|
100
100
|
request: RegistrationRequest,
|
|
@@ -105,10 +105,10 @@ export const registerPasskey = (
|
|
|
105
105
|
yield* _(capabilities.passkeySupport)
|
|
106
106
|
|
|
107
107
|
yield* _(E.logInfo('Fetching registration options from Passlock'))
|
|
108
|
-
const { options, session } = yield*
|
|
108
|
+
const { options, session } = yield* fetchOptions(new OptionsReq(request))
|
|
109
109
|
|
|
110
110
|
yield* _(E.logInfo('Building new credential'))
|
|
111
|
-
const createCredential = yield* _(CreateCredential)
|
|
111
|
+
const { createCredential } = yield* _(CreateCredential)
|
|
112
112
|
const credential = yield* _(createCredential(options))
|
|
113
113
|
|
|
114
114
|
yield* _(E.logInfo('Storing credential public key in Passlock'))
|
|
@@ -145,7 +145,9 @@ export const RegistrationServiceLive = Layer.effect(
|
|
|
145
145
|
RegistrationService,
|
|
146
146
|
E.gen(function* (_) {
|
|
147
147
|
const context = yield* _(
|
|
148
|
-
E.context<
|
|
148
|
+
E.context<
|
|
149
|
+
CreateCredential | RegistrationClient | Capabilities | StorageService | UserService
|
|
150
|
+
>(),
|
|
149
151
|
)
|
|
150
152
|
|
|
151
153
|
return RegistrationService.of({
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Context, Effect as E, Layer } from 'effect'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type AuthenticationService,
|
|
5
|
+
OPTIONS_ENDPOINT,
|
|
6
|
+
OptionsErrors,
|
|
7
|
+
OptionsReq,
|
|
8
|
+
OptionsRes,
|
|
9
|
+
VERIFY_ENDPOINT,
|
|
10
|
+
VerificationErrors,
|
|
11
|
+
VerificationReq,
|
|
12
|
+
VerificationRes,
|
|
13
|
+
} from '@passlock/shared/dist/rpc/authentication.js'
|
|
14
|
+
|
|
15
|
+
import { Dispatcher, makePostRequest } from './client.js'
|
|
16
|
+
|
|
17
|
+
/* Client */
|
|
18
|
+
|
|
19
|
+
export class AuthenticationClient extends Context.Tag('@passkey/auth/client')<
|
|
20
|
+
AuthenticationClient,
|
|
21
|
+
AuthenticationService
|
|
22
|
+
>() {}
|
|
23
|
+
|
|
24
|
+
export const AuthenticationClientLive = Layer.effect(
|
|
25
|
+
AuthenticationClient,
|
|
26
|
+
E.gen(function* (_) {
|
|
27
|
+
const dispatcher = yield* _(Dispatcher)
|
|
28
|
+
|
|
29
|
+
const optionsResolver = makePostRequest(OptionsReq, OptionsRes, OptionsErrors, dispatcher)
|
|
30
|
+
|
|
31
|
+
const verifyResolver = makePostRequest(
|
|
32
|
+
VerificationReq,
|
|
33
|
+
VerificationRes,
|
|
34
|
+
VerificationErrors,
|
|
35
|
+
dispatcher,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
getAuthenticationOptions: req => optionsResolver(OPTIONS_ENDPOINT, req),
|
|
40
|
+
verifyAuthenticationCredential: req => verifyResolver(VERIFY_ENDPOINT, req),
|
|
41
|
+
}
|
|
42
|
+
}),
|
|
43
|
+
)
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import * as S from '@effect/schema/Schema'
|
|
2
|
+
import { Context, Effect as E, Layer, pipe } from 'effect'
|
|
3
|
+
|
|
4
|
+
import { NetworkError } from '@passlock/shared/dist/error/error.js'
|
|
5
|
+
|
|
6
|
+
import { PASSLOCK_CLIENT_VERSION } from '../version.js'
|
|
7
|
+
import { RetrySchedule, RpcConfig } from './config.js'
|
|
8
|
+
|
|
9
|
+
export type DispatcherResponse = {
|
|
10
|
+
status: number
|
|
11
|
+
body: object
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** To send the JSON to the backend */
|
|
15
|
+
export class Dispatcher extends Context.Tag('@rpc/Dispatcher')<
|
|
16
|
+
Dispatcher,
|
|
17
|
+
{
|
|
18
|
+
get: (path: string) => E.Effect<DispatcherResponse, NetworkError>
|
|
19
|
+
post: (path: string, body: string) => E.Effect<DispatcherResponse, NetworkError>
|
|
20
|
+
}
|
|
21
|
+
>() {}
|
|
22
|
+
|
|
23
|
+
/** Fires off client requests using fetch */
|
|
24
|
+
/** TODO: Write tests */
|
|
25
|
+
/** TODO: Evaluate platform/http client (if now stable) */
|
|
26
|
+
export const DispatcherLive = Layer.effect(
|
|
27
|
+
Dispatcher,
|
|
28
|
+
E.gen(function* (_) {
|
|
29
|
+
const { schedule } = yield* _(RetrySchedule)
|
|
30
|
+
const { tenancyId, clientId, endpoint: maybeEndpoint } = yield* _(RpcConfig)
|
|
31
|
+
|
|
32
|
+
const parseJson = (res: Response, url: string) =>
|
|
33
|
+
E.tryPromise({
|
|
34
|
+
try: () => res.json() as Promise<unknown>,
|
|
35
|
+
catch: e =>
|
|
36
|
+
new NetworkError({
|
|
37
|
+
message: 'Unable to extract json response from ' + url,
|
|
38
|
+
detail: String(e),
|
|
39
|
+
}),
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// 400 errors are reflected in the RPC response error channel
|
|
43
|
+
// so in network terms they're still "ok"
|
|
44
|
+
const assertNo500s = (res: Response, url: string) => {
|
|
45
|
+
if (res.status >= 500) {
|
|
46
|
+
return E.fail(
|
|
47
|
+
new NetworkError({
|
|
48
|
+
message: 'Received 500 response code from ' + url,
|
|
49
|
+
}),
|
|
50
|
+
)
|
|
51
|
+
} else return E.void
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const parseJsonObject = (json: unknown) => {
|
|
55
|
+
return typeof json === 'object' && json !== null
|
|
56
|
+
? E.succeed(json)
|
|
57
|
+
: E.fail(
|
|
58
|
+
new NetworkError({
|
|
59
|
+
message: `Expected JSON object to be returned from RPC endpoint, actual ${typeof json}`,
|
|
60
|
+
}),
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const buildUrl = (_path: string) => {
|
|
65
|
+
const endpoint = maybeEndpoint || 'https://api.passlock.dev'
|
|
66
|
+
// drop leading /
|
|
67
|
+
const path = _path.replace(/^\//, '')
|
|
68
|
+
return `${endpoint}/${tenancyId}/${path}`
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
get: (path: string) => {
|
|
73
|
+
const effect = E.gen(function* (_) {
|
|
74
|
+
const headers = {
|
|
75
|
+
'Accept': 'application/json',
|
|
76
|
+
'X-CLIENT-ID': clientId,
|
|
77
|
+
'X-PASSLOCK-CLIENT-VERSION': PASSLOCK_CLIENT_VERSION,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const url = buildUrl(path)
|
|
81
|
+
|
|
82
|
+
const res = yield* _(
|
|
83
|
+
E.tryPromise({
|
|
84
|
+
try: () => fetch(url, { method: 'GET', headers }),
|
|
85
|
+
catch: e =>
|
|
86
|
+
new NetworkError({ message: 'Unable to fetch from ' + url, detail: String(e) }),
|
|
87
|
+
}),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
const json = yield* _(parseJson(res, url))
|
|
91
|
+
yield* _(assertNo500s(res, url))
|
|
92
|
+
const jsonObject = yield* _(parseJsonObject(json))
|
|
93
|
+
|
|
94
|
+
return { status: res.status, body: jsonObject }
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
return E.retry(effect, { schedule })
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
post: (_path: string, body: string) => {
|
|
101
|
+
const effect = E.gen(function* (_) {
|
|
102
|
+
const headers = {
|
|
103
|
+
'Content-Type': 'application/json',
|
|
104
|
+
'Accept': 'application/json',
|
|
105
|
+
'X-CLIENT-ID': clientId,
|
|
106
|
+
'X-PASSLOCK-CLIENT-VERSION': PASSLOCK_CLIENT_VERSION,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// drop leading /
|
|
110
|
+
const url = buildUrl(_path)
|
|
111
|
+
|
|
112
|
+
const res = yield* _(
|
|
113
|
+
E.tryPromise({
|
|
114
|
+
try: () => fetch(url, { method: 'POST', headers, body }),
|
|
115
|
+
catch: e =>
|
|
116
|
+
new NetworkError({ message: 'Unable to fetch from ' + url, detail: String(e) }),
|
|
117
|
+
}),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
const json = yield* _(parseJson(res, url))
|
|
121
|
+
yield* _(assertNo500s(res, url))
|
|
122
|
+
const jsonObject = yield* _(parseJsonObject(json))
|
|
123
|
+
|
|
124
|
+
return { status: res.status, body: jsonObject }
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
return E.retry(effect, { schedule })
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
}),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
export const makeGetRequest =
|
|
134
|
+
<Res, ResEnc, Err, ErrEnc>(
|
|
135
|
+
responseSchema: S.Schema<Res, ResEnc, never>,
|
|
136
|
+
errorSchema: S.Schema<Err, ErrEnc, never>,
|
|
137
|
+
dispatcher: Dispatcher['Type'],
|
|
138
|
+
) =>
|
|
139
|
+
(path: string) =>
|
|
140
|
+
pipe(
|
|
141
|
+
dispatcher.get(path),
|
|
142
|
+
E.flatMap(res => {
|
|
143
|
+
if (res.status === 200) return S.decodeUnknown(responseSchema)(res.body)
|
|
144
|
+
return pipe(
|
|
145
|
+
S.decodeUnknown(errorSchema)(res.body),
|
|
146
|
+
E.flatMap(err => E.fail(err)),
|
|
147
|
+
)
|
|
148
|
+
}),
|
|
149
|
+
E.catchTag('ParseError', e => E.die(e)),
|
|
150
|
+
E.catchTag('NetworkError', e => E.die(e)),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
export const makePostRequest =
|
|
154
|
+
<Req, ReqEnc, Res, ResEnc, Err, ErrEnc>(
|
|
155
|
+
requestSchema: S.Schema<Req, ReqEnc, never>,
|
|
156
|
+
responseSchema: S.Schema<Res, ResEnc, never>,
|
|
157
|
+
errorSchema: S.Schema<Err, ErrEnc, never>,
|
|
158
|
+
dispatcher: Dispatcher['Type'],
|
|
159
|
+
) =>
|
|
160
|
+
(path: string, request: Req) => {
|
|
161
|
+
return pipe(
|
|
162
|
+
S.encode(requestSchema)(request),
|
|
163
|
+
E.flatMap(request => dispatcher.post(path, JSON.stringify(request))),
|
|
164
|
+
E.flatMap(res => {
|
|
165
|
+
if (res.status === 200) return S.decodeUnknown(responseSchema)(res.body)
|
|
166
|
+
return pipe(
|
|
167
|
+
S.decodeUnknown(errorSchema)(res.body),
|
|
168
|
+
E.flatMap(err => E.fail(err)),
|
|
169
|
+
)
|
|
170
|
+
}),
|
|
171
|
+
E.catchTag('ParseError', e => E.die(e)),
|
|
172
|
+
E.catchTag('NetworkError', e => E.die(e)),
|
|
173
|
+
)
|
|
174
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Schedule } from 'effect'
|
|
2
|
+
import { Context } from 'effect'
|
|
3
|
+
|
|
4
|
+
export class RpcConfig extends Context.Tag('@rpc/RpcConfig')<
|
|
5
|
+
RpcConfig,
|
|
6
|
+
{
|
|
7
|
+
endpoint?: string
|
|
8
|
+
tenancyId: string
|
|
9
|
+
clientId: string
|
|
10
|
+
}
|
|
11
|
+
>() {}
|
|
12
|
+
|
|
13
|
+
export class RetrySchedule extends Context.Tag('@rpc/RetrySchedule')<
|
|
14
|
+
RetrySchedule,
|
|
15
|
+
{
|
|
16
|
+
schedule: Schedule.Schedule<unknown>
|
|
17
|
+
}
|
|
18
|
+
>() {}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as S from '@effect/schema/Schema'
|
|
2
|
+
import { Context, Effect as E, Layer } from 'effect'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
CONNECT_ENDPOINT,
|
|
6
|
+
ConnectRes,
|
|
7
|
+
type ConnectionService,
|
|
8
|
+
} from '@passlock/shared/dist/rpc/connection.js'
|
|
9
|
+
|
|
10
|
+
import { Dispatcher, makeGetRequest } from './client.js'
|
|
11
|
+
|
|
12
|
+
/* Client */
|
|
13
|
+
|
|
14
|
+
export class ConnectionClient extends Context.Tag('@connection/client')<
|
|
15
|
+
ConnectionClient,
|
|
16
|
+
ConnectionService
|
|
17
|
+
>() {}
|
|
18
|
+
|
|
19
|
+
export const ConnectionClientLive = Layer.effect(
|
|
20
|
+
ConnectionClient,
|
|
21
|
+
E.gen(function* (_) {
|
|
22
|
+
const dispatcher = yield* _(Dispatcher)
|
|
23
|
+
|
|
24
|
+
const preConnectResolver = makeGetRequest(ConnectRes, S.Never, dispatcher)
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
preConnect: () => preConnectResolver(CONNECT_ENDPOINT),
|
|
28
|
+
}
|
|
29
|
+
}),
|
|
30
|
+
)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Context, Effect as E, Layer } from 'effect'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
OptionsErrors,
|
|
5
|
+
OptionsReq,
|
|
6
|
+
OptionsRes,
|
|
7
|
+
type RegistrationService,
|
|
8
|
+
VerificationErrors,
|
|
9
|
+
VerificationReq,
|
|
10
|
+
VerificationRes,
|
|
11
|
+
} from '@passlock/shared/dist/rpc/registration.js'
|
|
12
|
+
|
|
13
|
+
import { Dispatcher, makePostRequest } from './client.js'
|
|
14
|
+
|
|
15
|
+
/* Client */
|
|
16
|
+
|
|
17
|
+
export class RegistrationClient extends Context.Tag('@passkey/register/client')<
|
|
18
|
+
RegistrationClient,
|
|
19
|
+
RegistrationService
|
|
20
|
+
>() {}
|
|
21
|
+
|
|
22
|
+
export const RegistrationClientLive = Layer.effect(
|
|
23
|
+
RegistrationClient,
|
|
24
|
+
E.gen(function* (_) {
|
|
25
|
+
const dispatcher = yield* _(Dispatcher)
|
|
26
|
+
|
|
27
|
+
const optionsResolver = makePostRequest(OptionsReq, OptionsRes, OptionsErrors, dispatcher)
|
|
28
|
+
|
|
29
|
+
const verifyResolver = makePostRequest(
|
|
30
|
+
VerificationReq,
|
|
31
|
+
VerificationRes,
|
|
32
|
+
VerificationErrors,
|
|
33
|
+
dispatcher,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
getRegistrationOptions: req => optionsResolver('/passkey/register/options', req),
|
|
38
|
+
verifyRegistrationCredential: req => verifyResolver('/passkey/register/verify', req),
|
|
39
|
+
}
|
|
40
|
+
}),
|
|
41
|
+
)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Context, Effect as E, Layer } from 'effect'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AuthOidcErrors,
|
|
5
|
+
AuthOidcReq,
|
|
6
|
+
PrincipalRes,
|
|
7
|
+
RegisterOidcErrors,
|
|
8
|
+
RegisterOidcReq,
|
|
9
|
+
type SocialService,
|
|
10
|
+
} from '@passlock/shared/dist/rpc/social.js'
|
|
11
|
+
|
|
12
|
+
import { Dispatcher, makePostRequest } from './client.js'
|
|
13
|
+
|
|
14
|
+
/* Client */
|
|
15
|
+
|
|
16
|
+
export const OIDC_REGISTER_ENDPOINT = '/social/oidc/register'
|
|
17
|
+
export const OIDC_AUTH_ENDPOINT = '/social/oidc/auth'
|
|
18
|
+
|
|
19
|
+
export class SocialClient extends Context.Tag('@social/client')<SocialClient, SocialService>() {}
|
|
20
|
+
|
|
21
|
+
export const SocialClientLive = Layer.effect(
|
|
22
|
+
SocialClient,
|
|
23
|
+
E.gen(function* (_) {
|
|
24
|
+
const dispatcher = yield* _(Dispatcher)
|
|
25
|
+
|
|
26
|
+
const registerResolver = makePostRequest(
|
|
27
|
+
RegisterOidcReq,
|
|
28
|
+
PrincipalRes,
|
|
29
|
+
RegisterOidcErrors,
|
|
30
|
+
dispatcher,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
const authenticateResolver = makePostRequest(
|
|
34
|
+
AuthOidcReq,
|
|
35
|
+
PrincipalRes,
|
|
36
|
+
AuthOidcErrors,
|
|
37
|
+
dispatcher,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
registerOidc: req => registerResolver(OIDC_REGISTER_ENDPOINT, req),
|
|
42
|
+
authenticateOidc: req => authenticateResolver(OIDC_AUTH_ENDPOINT, req),
|
|
43
|
+
}
|
|
44
|
+
}),
|
|
45
|
+
)
|
package/src/rpc/user.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as S from '@effect/schema/Schema'
|
|
2
|
+
import { Context, Effect as E, Layer } from 'effect'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
IsExistingUserReq,
|
|
6
|
+
IsExistingUserRes,
|
|
7
|
+
RESEND_EMAIL_ENDPOINT,
|
|
8
|
+
ResendEmailErrors,
|
|
9
|
+
ResendEmailReq,
|
|
10
|
+
ResendEmailRes,
|
|
11
|
+
USER_STATUS_ENDPOINT,
|
|
12
|
+
type UserService,
|
|
13
|
+
VERIFY_EMAIL_ENDPOINT,
|
|
14
|
+
VerifyEmailErrors,
|
|
15
|
+
VerifyEmailReq,
|
|
16
|
+
VerifyEmailRes,
|
|
17
|
+
} from '@passlock/shared/dist/rpc/user.js'
|
|
18
|
+
|
|
19
|
+
import { Dispatcher, makePostRequest } from './client.js'
|
|
20
|
+
|
|
21
|
+
/* Client */
|
|
22
|
+
|
|
23
|
+
export class UserClient extends Context.Tag('@user/client')<UserClient, UserService>() {}
|
|
24
|
+
|
|
25
|
+
export const UserClientLive = Layer.effect(
|
|
26
|
+
UserClient,
|
|
27
|
+
E.gen(function* (_) {
|
|
28
|
+
const dispatcher = yield* _(Dispatcher)
|
|
29
|
+
|
|
30
|
+
const isExistingUserResolver = makePostRequest(
|
|
31
|
+
IsExistingUserReq,
|
|
32
|
+
IsExistingUserRes,
|
|
33
|
+
S.Never,
|
|
34
|
+
dispatcher,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
const verifyEmailResolver = makePostRequest(
|
|
38
|
+
VerifyEmailReq,
|
|
39
|
+
VerifyEmailRes,
|
|
40
|
+
VerifyEmailErrors,
|
|
41
|
+
dispatcher,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const resendEmailResolver = makePostRequest(
|
|
45
|
+
ResendEmailReq,
|
|
46
|
+
ResendEmailRes,
|
|
47
|
+
ResendEmailErrors,
|
|
48
|
+
dispatcher,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
isExistingUser: req => isExistingUserResolver(USER_STATUS_ENDPOINT, req),
|
|
53
|
+
verifyEmail: req => verifyEmailResolver(VERIFY_EMAIL_ENDPOINT, req),
|
|
54
|
+
resendVerificationEmail: req => resendEmailResolver(RESEND_EMAIL_ENDPOINT, req),
|
|
55
|
+
}
|
|
56
|
+
}),
|
|
57
|
+
)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { Effect as E, Layer as L, Option as O } from 'effect'
|
|
2
|
+
|
|
1
3
|
import * as Shared from '@passlock/shared/dist/rpc/social.js'
|
|
2
|
-
|
|
3
|
-
import { Effect as E, Layer as L } from 'effect'
|
|
4
|
+
|
|
4
5
|
import * as Fixtures from '../test/fixtures.js'
|
|
5
|
-
import
|
|
6
|
+
import { SocialClient } from '../rpc/social.js'
|
|
7
|
+
import type { AuthenticateOidcReq } from './social.js'
|
|
6
8
|
|
|
7
9
|
export const session = 'session'
|
|
8
10
|
export const token = 'token'
|
|
@@ -10,30 +12,22 @@ export const code = 'code'
|
|
|
10
12
|
export const authType = 'passkey'
|
|
11
13
|
export const expireAt = Date.now() + 10000
|
|
12
14
|
|
|
13
|
-
export const registerOidcReq
|
|
15
|
+
export const registerOidcReq = new Shared.RegisterOidcReq({
|
|
14
16
|
provider: 'google',
|
|
15
17
|
idToken: 'google-token',
|
|
16
18
|
nonce: 'nonce',
|
|
17
|
-
givenName: 'john',
|
|
18
|
-
familyName: 'doe'
|
|
19
|
-
}
|
|
19
|
+
givenName: O.some('john'),
|
|
20
|
+
familyName: O.some('doe'),
|
|
21
|
+
})
|
|
20
22
|
|
|
21
|
-
export const authOidcReq: AuthenticateOidcReq = {
|
|
23
|
+
export const authOidcReq: AuthenticateOidcReq = new Shared.AuthOidcReq({
|
|
22
24
|
provider: 'google',
|
|
23
25
|
idToken: 'google-token',
|
|
24
|
-
nonce: 'nonce'
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const rpcRegisterReq = new Shared.RegisterOidcReq({
|
|
28
|
-
...registerOidcReq,
|
|
29
|
-
...(registerOidcReq.givenName ? { givenName: registerOidcReq.givenName } : {}),
|
|
30
|
-
...(registerOidcReq.familyName ? { familyName: registerOidcReq.familyName } : {})
|
|
26
|
+
nonce: 'nonce',
|
|
31
27
|
})
|
|
32
28
|
|
|
33
29
|
export const rpcRegisterRes = new Shared.PrincipalRes({ principal: Fixtures.principal })
|
|
34
30
|
|
|
35
|
-
export const rpcAuthenticateReq = new Shared.AuthOidcReq({ ...authOidcReq })
|
|
36
|
-
|
|
37
31
|
export const rpcAuthenticateRes = new Shared.PrincipalRes({ principal: Fixtures.principal })
|
|
38
32
|
|
|
39
33
|
export const rpcClientTest = L.succeed(
|
|
@@ -41,7 +35,7 @@ export const rpcClientTest = L.succeed(
|
|
|
41
35
|
SocialClient.of({
|
|
42
36
|
registerOidc: () => E.fail(Fixtures.notImplemented),
|
|
43
37
|
authenticateOidc: () => E.fail(Fixtures.notImplemented),
|
|
44
|
-
})
|
|
38
|
+
}),
|
|
45
39
|
)
|
|
46
40
|
|
|
47
41
|
export const principal = Fixtures.principal
|