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