@passlock/client 0.9.17 → 0.9.21
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 +5 -4
- package/dist/authentication/authenticate.d.ts.map +1 -1
- package/dist/authentication/authenticate.fixture.d.ts +4 -5
- package/dist/authentication/authenticate.fixture.d.ts.map +1 -1
- package/dist/authentication/authenticate.fixture.js +2 -11
- package/dist/authentication/authenticate.fixture.js.map +1 -1
- package/dist/authentication/authenticate.js +4 -4
- package/dist/authentication/authenticate.js.map +1 -1
- package/dist/authentication/authenticate.test.js +5 -5
- package/dist/authentication/authenticate.test.js.map +1 -1
- package/dist/connection/connection.d.ts +4 -3
- package/dist/connection/connection.d.ts.map +1 -1
- package/dist/connection/connection.fixture.d.ts +3 -5
- package/dist/connection/connection.fixture.d.ts.map +1 -1
- package/dist/connection/connection.fixture.js +3 -15
- package/dist/connection/connection.fixture.js.map +1 -1
- package/dist/connection/connection.js +3 -3
- package/dist/connection/connection.js.map +1 -1
- package/dist/connection/connection.test.js +6 -5
- package/dist/connection/connection.test.js.map +1 -1
- package/dist/effect.d.ts +4 -4
- package/dist/effect.d.ts.map +1 -1
- package/dist/effect.js +17 -8
- package/dist/effect.js.map +1 -1
- package/dist/email/email.d.ts +5 -5
- package/dist/email/email.d.ts.map +1 -1
- package/dist/email/email.fixture.d.ts +3 -4
- package/dist/email/email.fixture.d.ts.map +1 -1
- package/dist/email/email.fixture.js +2 -10
- package/dist/email/email.fixture.js.map +1 -1
- package/dist/email/email.js +2 -3
- package/dist/email/email.js.map +1 -1
- package/dist/email/email.test.js +5 -5
- package/dist/email/email.test.js.map +1 -1
- package/dist/index.d.ts +13 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/logging/eventLogger.d.ts.map +1 -1
- package/dist/logging/eventLogger.js +15 -9
- package/dist/logging/eventLogger.js.map +1 -1
- package/dist/logging/eventLogger.test.js.map +1 -1
- package/dist/registration/register.d.ts +6 -4
- package/dist/registration/register.d.ts.map +1 -1
- package/dist/registration/register.fixture.d.ts +4 -5
- package/dist/registration/register.fixture.d.ts.map +1 -1
- package/dist/registration/register.fixture.js +2 -12
- package/dist/registration/register.fixture.js.map +1 -1
- package/dist/registration/register.js +5 -4
- package/dist/registration/register.js.map +1 -1
- package/dist/registration/register.test.js +6 -6
- package/dist/registration/register.test.js.map +1 -1
- package/dist/rpc/authentication.d.ts +9 -0
- package/dist/rpc/authentication.d.ts.map +1 -0
- package/dist/rpc/authentication.js +15 -0
- package/dist/rpc/authentication.js.map +1 -0
- package/dist/rpc/client.d.ts +27 -0
- package/dist/rpc/client.d.ts.map +1 -0
- package/dist/rpc/client.js +92 -0
- package/dist/rpc/client.js.map +1 -0
- package/dist/rpc/config.d.ts +16 -0
- package/dist/rpc/config.d.ts.map +1 -0
- package/dist/rpc/config.js +6 -0
- package/dist/rpc/config.js.map +1 -0
- package/dist/rpc/connection.d.ts +9 -0
- package/dist/rpc/connection.d.ts.map +1 -0
- package/dist/rpc/connection.js +14 -0
- package/dist/rpc/connection.js.map +1 -0
- package/dist/rpc/registration.d.ts +9 -0
- package/dist/rpc/registration.d.ts.map +1 -0
- package/dist/rpc/registration.js +15 -0
- package/dist/rpc/registration.js.map +1 -0
- package/dist/rpc/social.d.ts +11 -0
- package/dist/rpc/social.d.ts.map +1 -0
- package/dist/rpc/social.js +17 -0
- package/dist/rpc/social.js.map +1 -0
- package/dist/rpc/user.d.ts +9 -0
- package/dist/rpc/user.d.ts.map +1 -0
- package/dist/rpc/user.js +18 -0
- package/dist/rpc/user.js.map +1 -0
- package/dist/social/social.d.ts +22 -13
- package/dist/social/social.d.ts.map +1 -1
- package/dist/social/social.fixture.d.ts +10 -10
- package/dist/social/social.fixture.d.ts.map +1 -1
- package/dist/social/social.fixture.js +21 -19
- package/dist/social/social.fixture.js.map +1 -1
- package/dist/social/social.js +12 -6
- package/dist/social/social.js.map +1 -1
- package/dist/social/social.test.js +15 -15
- package/dist/social/social.test.js.map +1 -1
- package/dist/storage/storage.d.ts +2 -2
- package/dist/storage/storage.d.ts.map +1 -1
- package/dist/storage/storage.js +1 -0
- package/dist/storage/storage.js.map +1 -1
- package/dist/test/fixtures.d.ts +1 -3
- package/dist/test/fixtures.d.ts.map +1 -1
- package/dist/test/fixtures.js +0 -2
- package/dist/test/fixtures.js.map +1 -1
- package/dist/user/user.d.ts +4 -4
- package/dist/user/user.d.ts.map +1 -1
- package/dist/user/user.fixture.d.ts +2 -3
- package/dist/user/user.fixture.d.ts.map +1 -1
- package/dist/user/user.fixture.js +2 -10
- package/dist/user/user.fixture.js.map +1 -1
- package/dist/user/user.js +3 -4
- package/dist/user/user.js.map +1 -1
- package/dist/user/user.test.js +5 -5
- package/dist/user/user.test.js.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +10 -11
- package/src/authentication/authenticate.fixture.ts +8 -16
- package/src/authentication/authenticate.test.ts +7 -7
- package/src/authentication/authenticate.ts +13 -14
- package/src/connection/connection.fixture.ts +4 -16
- package/src/connection/connection.test.ts +7 -6
- package/src/connection/connection.ts +5 -5
- package/src/effect.ts +22 -16
- package/src/email/email.fixture.ts +3 -11
- package/src/email/email.test.ts +7 -7
- package/src/email/email.ts +5 -6
- package/src/index.ts +10 -7
- package/src/logging/eventLogger.test.ts +1 -0
- package/src/logging/eventLogger.ts +16 -10
- package/src/registration/register.fixture.ts +9 -18
- package/src/registration/register.test.ts +9 -9
- package/src/registration/register.ts +12 -13
- package/src/social/social.fixture.ts +25 -21
- package/src/social/social.test.ts +21 -21
- package/src/social/social.ts +40 -19
- package/src/storage/storage.ts +3 -2
- package/src/test/fixtures.ts +1 -3
- package/src/user/user.fixture.ts +3 -11
- package/src/user/user.test.ts +7 -7
- package/src/user/user.ts +6 -7
- package/README.md +0 -116
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { AuthenticateOidcReq, OidcRes, RegisterOidcReq } from '@passlock/shared/dist/rpc/social.js'
|
|
1
|
+
import * as Shared from '@passlock/shared/dist/rpc/social.js'
|
|
2
|
+
import { SocialClient } from '@passlock/shared/dist/rpc/social.js'
|
|
4
3
|
import { Effect as E, Layer as L } from 'effect'
|
|
5
4
|
import * as Fixtures from '../test/fixtures.js'
|
|
6
|
-
import {
|
|
5
|
+
import type { AuthenticateOidcReq, RegisterOidcReq } from './social.js'
|
|
7
6
|
|
|
8
7
|
export const session = 'session'
|
|
9
8
|
export const token = 'token'
|
|
@@ -11,33 +10,38 @@ export const code = 'code'
|
|
|
11
10
|
export const authType = 'passkey'
|
|
12
11
|
export const expireAt = Date.now() + 10000
|
|
13
12
|
|
|
14
|
-
export const
|
|
13
|
+
export const registerOidcReq: RegisterOidcReq = {
|
|
15
14
|
provider: 'google',
|
|
16
|
-
idToken: 'google-token'
|
|
15
|
+
idToken: 'google-token',
|
|
16
|
+
nonce: 'nonce',
|
|
17
|
+
givenName: 'john',
|
|
18
|
+
familyName: 'doe'
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
export const
|
|
21
|
+
export const authOidcReq: AuthenticateOidcReq = {
|
|
22
|
+
provider: 'google',
|
|
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 } : {})
|
|
31
|
+
})
|
|
20
32
|
|
|
21
|
-
export const rpcRegisterRes = new
|
|
33
|
+
export const rpcRegisterRes = new Shared.PrincipalRes({ principal: Fixtures.principal })
|
|
22
34
|
|
|
23
|
-
export const rpcAuthenticateReq = new
|
|
35
|
+
export const rpcAuthenticateReq = new Shared.AuthOidcReq({ ...authOidcReq })
|
|
24
36
|
|
|
25
|
-
export const rpcAuthenticateRes = new
|
|
37
|
+
export const rpcAuthenticateRes = new Shared.PrincipalRes({ principal: Fixtures.principal })
|
|
26
38
|
|
|
27
39
|
export const rpcClientTest = L.succeed(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
preConnect: () => E.succeed(new PreConnectRes({ warmed: true })),
|
|
31
|
-
isExistingUser: () => E.fail(Fixtures.notImplemented),
|
|
32
|
-
verifyEmail: () => E.fail(Fixtures.notImplemented),
|
|
33
|
-
getRegistrationOptions: () => E.fail(Fixtures.notImplemented),
|
|
34
|
-
verifyRegistrationCredential: () => E.fail(Fixtures.notImplemented),
|
|
35
|
-
getAuthenticationOptions: () => E.fail(Fixtures.notImplemented),
|
|
36
|
-
verifyAuthenticationCredential: () => E.fail(Fixtures.notImplemented),
|
|
40
|
+
SocialClient,
|
|
41
|
+
SocialClient.of({
|
|
37
42
|
registerOidc: () => E.fail(Fixtures.notImplemented),
|
|
38
43
|
authenticateOidc: () => E.fail(Fixtures.notImplemented),
|
|
39
|
-
|
|
40
|
-
}),
|
|
44
|
+
})
|
|
41
45
|
)
|
|
42
46
|
|
|
43
47
|
export const principal = Fixtures.principal
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Duplicate, NotFound } from '@passlock/shared/dist/error/error.js'
|
|
2
|
-
import {
|
|
2
|
+
import { SocialClient } from '@passlock/shared/dist/rpc/social.js'
|
|
3
3
|
import { Effect as E, Layer as L, Layer, LogLevel, Logger, pipe } from 'effect'
|
|
4
4
|
import { describe, expect, test } from 'vitest'
|
|
5
5
|
import { mock } from 'vitest-mock-extended'
|
|
@@ -10,14 +10,14 @@ describe('registerOidc should', () => {
|
|
|
10
10
|
test('return a valid credential', async () => {
|
|
11
11
|
const assertions = E.gen(function* (_) {
|
|
12
12
|
const service = yield* _(SocialService)
|
|
13
|
-
const result = yield* _(service.registerOidc(Fixture.
|
|
13
|
+
const result = yield* _(service.registerOidc(Fixture.registerOidcReq))
|
|
14
14
|
expect(result).toEqual(Fixture.principal)
|
|
15
15
|
})
|
|
16
16
|
|
|
17
17
|
const rpcClientTest = L.effect(
|
|
18
|
-
|
|
18
|
+
SocialClient,
|
|
19
19
|
E.sync(() => {
|
|
20
|
-
const rpcMock = mock<
|
|
20
|
+
const rpcMock = mock<SocialClient['Type']>()
|
|
21
21
|
|
|
22
22
|
rpcMock.registerOidc.mockReturnValue(E.succeed(Fixture.rpcRegisterRes))
|
|
23
23
|
|
|
@@ -39,16 +39,16 @@ describe('registerOidc should', () => {
|
|
|
39
39
|
test('pass the request to the backend', async () => {
|
|
40
40
|
const assertions = E.gen(function* (_) {
|
|
41
41
|
const service = yield* _(SocialService)
|
|
42
|
-
yield* _(service.registerOidc(Fixture.
|
|
42
|
+
yield* _(service.registerOidc(Fixture.registerOidcReq))
|
|
43
43
|
|
|
44
|
-
const rpcClient = yield* _(
|
|
44
|
+
const rpcClient = yield* _(SocialClient)
|
|
45
45
|
expect(rpcClient.registerOidc).toHaveBeenCalledWith(Fixture.rpcRegisterReq)
|
|
46
46
|
})
|
|
47
47
|
|
|
48
48
|
const rpcClientTest = L.effect(
|
|
49
|
-
|
|
49
|
+
SocialClient,
|
|
50
50
|
E.sync(() => {
|
|
51
|
-
const rpcMock = mock<
|
|
51
|
+
const rpcMock = mock<SocialClient['Type']>()
|
|
52
52
|
|
|
53
53
|
rpcMock.registerOidc.mockReturnValue(E.succeed(Fixture.rpcRegisterRes))
|
|
54
54
|
|
|
@@ -71,15 +71,15 @@ describe('registerOidc should', () => {
|
|
|
71
71
|
const assertions = E.gen(function* (_) {
|
|
72
72
|
const service = yield* _(SocialService)
|
|
73
73
|
|
|
74
|
-
const defect = yield* _(service.registerOidc(Fixture.
|
|
74
|
+
const defect = yield* _(service.registerOidc(Fixture.registerOidcReq), E.flip)
|
|
75
75
|
|
|
76
76
|
expect(defect).toBeInstanceOf(Duplicate)
|
|
77
77
|
})
|
|
78
78
|
|
|
79
79
|
const rpcClientTest = L.effect(
|
|
80
|
-
|
|
80
|
+
SocialClient,
|
|
81
81
|
E.sync(() => {
|
|
82
|
-
const rpcMock = mock<
|
|
82
|
+
const rpcMock = mock<SocialClient['Type']>()
|
|
83
83
|
|
|
84
84
|
rpcMock.registerOidc.mockReturnValue(E.fail(new Duplicate({ message: "Duplicate user" })))
|
|
85
85
|
|
|
@@ -103,14 +103,14 @@ describe('authenticateIodc should', () => {
|
|
|
103
103
|
test('return a valid credential', async () => {
|
|
104
104
|
const assertions = E.gen(function* (_) {
|
|
105
105
|
const service = yield* _(SocialService)
|
|
106
|
-
const result = yield* _(service.authenticateOidc(Fixture.
|
|
106
|
+
const result = yield* _(service.authenticateOidc(Fixture.authOidcReq))
|
|
107
107
|
expect(result).toEqual(Fixture.principal)
|
|
108
108
|
})
|
|
109
109
|
|
|
110
110
|
const rpcClientTest = L.effect(
|
|
111
|
-
|
|
111
|
+
SocialClient,
|
|
112
112
|
E.sync(() => {
|
|
113
|
-
const rpcMock = mock<
|
|
113
|
+
const rpcMock = mock<SocialClient['Type']>()
|
|
114
114
|
|
|
115
115
|
rpcMock.authenticateOidc.mockReturnValue(E.succeed(Fixture.rpcRegisterRes))
|
|
116
116
|
|
|
@@ -132,16 +132,16 @@ describe('authenticateIodc should', () => {
|
|
|
132
132
|
test('pass the request to the backend', async () => {
|
|
133
133
|
const assertions = E.gen(function* (_) {
|
|
134
134
|
const service = yield* _(SocialService)
|
|
135
|
-
yield* _(service.authenticateOidc(Fixture.
|
|
135
|
+
yield* _(service.authenticateOidc(Fixture.authOidcReq))
|
|
136
136
|
|
|
137
|
-
const rpcClient = yield* _(
|
|
137
|
+
const rpcClient = yield* _(SocialClient)
|
|
138
138
|
expect(rpcClient.authenticateOidc).toHaveBeenCalledWith(Fixture.rpcAuthenticateReq)
|
|
139
139
|
})
|
|
140
140
|
|
|
141
141
|
const rpcClientTest = L.effect(
|
|
142
|
-
|
|
142
|
+
SocialClient,
|
|
143
143
|
E.sync(() => {
|
|
144
|
-
const rpcMock = mock<
|
|
144
|
+
const rpcMock = mock<SocialClient['Type']>()
|
|
145
145
|
|
|
146
146
|
rpcMock.authenticateOidc.mockReturnValue(E.succeed(Fixture.rpcAuthenticateRes))
|
|
147
147
|
|
|
@@ -164,15 +164,15 @@ describe('authenticateIodc should', () => {
|
|
|
164
164
|
const assertions = E.gen(function* (_) {
|
|
165
165
|
const service = yield* _(SocialService)
|
|
166
166
|
|
|
167
|
-
const defect = yield* _(service.authenticateOidc(Fixture.
|
|
167
|
+
const defect = yield* _(service.authenticateOidc(Fixture.authOidcReq), E.flip)
|
|
168
168
|
|
|
169
169
|
expect(defect).toBeInstanceOf(NotFound)
|
|
170
170
|
})
|
|
171
171
|
|
|
172
172
|
const rpcClientTest = L.effect(
|
|
173
|
-
|
|
173
|
+
SocialClient,
|
|
174
174
|
E.sync(() => {
|
|
175
|
-
const rpcMock = mock<
|
|
175
|
+
const rpcMock = mock<SocialClient['Type']>()
|
|
176
176
|
|
|
177
177
|
rpcMock.authenticateOidc.mockReturnValue(E.fail(new NotFound({ message: "User not found" })))
|
|
178
178
|
|
package/src/social/social.ts
CHANGED
|
@@ -2,31 +2,45 @@
|
|
|
2
2
|
* Passkey authentication effects
|
|
3
3
|
*/
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
type BadRequest,
|
|
6
|
+
type NotSupported
|
|
7
7
|
} from '@passlock/shared/dist/error/error.js'
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
8
|
+
import * as Shared from '@passlock/shared/dist/rpc/social.js'
|
|
9
|
+
import { SocialClient } from '@passlock/shared/dist/rpc/social.js'
|
|
10
10
|
import type {
|
|
11
|
-
|
|
12
|
-
} from '@passlock/shared/dist/schema/
|
|
11
|
+
Principal
|
|
12
|
+
} from '@passlock/shared/dist/schema/principal.js'
|
|
13
13
|
import { Context, Effect as E, Layer, flow } from 'effect'
|
|
14
14
|
|
|
15
15
|
/* Requests */
|
|
16
16
|
|
|
17
|
-
export type
|
|
17
|
+
export type Provider = 'apple' | 'google'
|
|
18
|
+
|
|
19
|
+
export type RegisterOidcReq = {
|
|
20
|
+
provider: Provider
|
|
21
|
+
idToken: string
|
|
22
|
+
nonce: string
|
|
23
|
+
givenName?: string
|
|
24
|
+
familyName?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type AuthenticateOidcReq = {
|
|
28
|
+
provider: Provider
|
|
29
|
+
idToken: string
|
|
30
|
+
nonce: string
|
|
31
|
+
}
|
|
18
32
|
|
|
19
33
|
/* Errors */
|
|
20
34
|
|
|
21
|
-
export type RegistrationErrors = NotSupported | BadRequest | RegisterOidcErrors
|
|
35
|
+
export type RegistrationErrors = NotSupported | BadRequest | Shared.RegisterOidcErrors
|
|
22
36
|
|
|
23
|
-
export type AuthenticationErrors = NotSupported | BadRequest |
|
|
37
|
+
export type AuthenticationErrors = NotSupported | BadRequest | Shared.AuthOidcErrors
|
|
24
38
|
|
|
25
39
|
/* Service */
|
|
26
40
|
|
|
27
41
|
export type SocialService = {
|
|
28
|
-
registerOidc: (data:
|
|
29
|
-
authenticateOidc: (data:
|
|
42
|
+
registerOidc: (data: RegisterOidcReq) => E.Effect<Principal, RegistrationErrors>
|
|
43
|
+
authenticateOidc: (data: AuthenticateOidcReq) => E.Effect<Principal, AuthenticationErrors>
|
|
30
44
|
}
|
|
31
45
|
|
|
32
46
|
export const SocialService = Context.GenericTag<SocialService>(
|
|
@@ -35,18 +49,24 @@ export const SocialService = Context.GenericTag<SocialService>(
|
|
|
35
49
|
|
|
36
50
|
/* Effects */
|
|
37
51
|
|
|
38
|
-
type Dependencies =
|
|
52
|
+
type Dependencies = SocialClient
|
|
39
53
|
|
|
40
54
|
export const registerOidc = (
|
|
41
|
-
request:
|
|
55
|
+
request: RegisterOidcReq,
|
|
42
56
|
): E.Effect<Principal, RegistrationErrors, Dependencies> => {
|
|
43
57
|
return E.gen(function* (_) {
|
|
44
58
|
yield* _(E.logInfo('Registering social account'))
|
|
45
59
|
|
|
46
|
-
const rpcClient = yield* _(
|
|
60
|
+
const rpcClient = yield* _(SocialClient)
|
|
61
|
+
|
|
62
|
+
const rpcRequest = new Shared.RegisterOidcReq({
|
|
63
|
+
...request,
|
|
64
|
+
...(request.givenName ? { givenName: request.givenName } : {}),
|
|
65
|
+
...(request.familyName ? { familyName: request.familyName } : {})
|
|
66
|
+
})
|
|
47
67
|
|
|
48
68
|
const { principal } = yield* _(
|
|
49
|
-
rpcClient.registerOidc(
|
|
69
|
+
rpcClient.registerOidc(rpcRequest)
|
|
50
70
|
)
|
|
51
71
|
|
|
52
72
|
return principal
|
|
@@ -54,15 +74,16 @@ export const registerOidc = (
|
|
|
54
74
|
}
|
|
55
75
|
|
|
56
76
|
export const authenticateOidc = (
|
|
57
|
-
request:
|
|
77
|
+
request: AuthenticateOidcReq,
|
|
58
78
|
): E.Effect<Principal, AuthenticationErrors, Dependencies> => {
|
|
59
79
|
return E.gen(function* (_) {
|
|
60
80
|
yield* _(E.logInfo('Authenticating with social account'))
|
|
61
81
|
|
|
62
|
-
const rpcClient = yield* _(
|
|
82
|
+
const rpcClient = yield* _(SocialClient)
|
|
83
|
+
const rpcRequest = new Shared.AuthOidcReq(request)
|
|
63
84
|
|
|
64
85
|
const { principal } = yield* _(
|
|
65
|
-
rpcClient.authenticateOidc(
|
|
86
|
+
rpcClient.authenticateOidc(rpcRequest)
|
|
66
87
|
)
|
|
67
88
|
|
|
68
89
|
return principal
|
|
@@ -75,7 +96,7 @@ export const authenticateOidc = (
|
|
|
75
96
|
export const SocialServiceLive = Layer.effect(
|
|
76
97
|
SocialService,
|
|
77
98
|
E.gen(function* (_) {
|
|
78
|
-
const context = yield* _(E.context<
|
|
99
|
+
const context = yield* _(E.context<SocialClient>())
|
|
79
100
|
|
|
80
101
|
return SocialService.of({
|
|
81
102
|
registerOidc: flow(registerOidc, E.provide(context)),
|
package/src/storage/storage.ts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* Wrapper around local storage that allows us to store
|
|
3
3
|
* authentication tokens in local storage for a short period.
|
|
4
4
|
*/
|
|
5
|
-
import type { Principal } from '@passlock/shared/dist/schema/
|
|
5
|
+
import type { Principal } from '@passlock/shared/dist/schema/principal.js'
|
|
6
6
|
import { Context, Effect as E, Layer, Option as O, flow, pipe } from 'effect'
|
|
7
7
|
import type { NoSuchElementException } from 'effect/Cause'
|
|
8
8
|
|
|
9
9
|
/* Requests */
|
|
10
10
|
|
|
11
|
-
export type AuthType = 'email' | 'passkey' | 'google'
|
|
11
|
+
export type AuthType = 'email' | 'passkey' | 'apple' | 'google'
|
|
12
12
|
|
|
13
13
|
export type StoredToken = {
|
|
14
14
|
token: string
|
|
@@ -144,6 +144,7 @@ export const clearExpiredTokens: E.Effect<void, never, Storage> = E.all([
|
|
|
144
144
|
clearExpiredToken('passkey'),
|
|
145
145
|
clearExpiredToken('email'),
|
|
146
146
|
clearExpiredToken('google'),
|
|
147
|
+
clearExpiredToken('apple'),
|
|
147
148
|
])
|
|
148
149
|
|
|
149
150
|
/* Live */
|
package/src/test/fixtures.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { BadRequest } from '@passlock/shared/dist/error/error.js'
|
|
2
|
-
import {
|
|
3
|
-
import type { Principal } from '@passlock/shared/dist/schema/schema.js'
|
|
2
|
+
import type { Principal } from '@passlock/shared/dist/schema/principal.js'
|
|
4
3
|
import { Effect as E, Layer as L } from 'effect'
|
|
5
4
|
import { Capabilities } from '../capabilities/capabilities.js'
|
|
6
5
|
import { StorageService, type StoredToken } from '../storage/storage.js'
|
|
@@ -51,5 +50,4 @@ export const storageServiceTest = L.succeed(
|
|
|
51
50
|
}),
|
|
52
51
|
)
|
|
53
52
|
|
|
54
|
-
export const preConnectRes = new PreConnectRes({ warmed: true })
|
|
55
53
|
export const notImplemented = new BadRequest({ message: 'Not implemented' })
|
package/src/user/user.fixture.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { IsExistingUserReq, IsExistingUserRes, ResendEmailReq, ResendEmailRes, VerifyEmailRes } from '@passlock/shared/dist/rpc/user.js'
|
|
1
|
+
import { IsExistingUserReq, IsExistingUserRes, ResendEmailReq, ResendEmailRes, UserClient, VerifyEmailRes } from '@passlock/shared/dist/rpc/user.js'
|
|
3
2
|
import { Effect as E, Layer as L } from 'effect'
|
|
4
3
|
import * as Fixtures from '../test/fixtures.js'
|
|
5
4
|
import type { ResendEmail } from './user.js'
|
|
@@ -13,17 +12,10 @@ export const rpcResendEmailReq = new ResendEmailReq({ userId: '123', verifyEmail
|
|
|
13
12
|
export const rpcResendEmailRes = new ResendEmailRes({ })
|
|
14
13
|
|
|
15
14
|
export const rpcClientTest = L.succeed(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
preConnect: () => E.succeed({ warmed: true }),
|
|
15
|
+
UserClient,
|
|
16
|
+
UserClient.of({
|
|
19
17
|
isExistingUser: () => E.succeed({ existingUser: true }),
|
|
20
18
|
verifyEmail: () => E.succeed(verifyEmailRes),
|
|
21
|
-
getRegistrationOptions: () => E.fail(Fixtures.notImplemented),
|
|
22
|
-
verifyRegistrationCredential: () => E.fail(Fixtures.notImplemented),
|
|
23
|
-
getAuthenticationOptions: () => E.fail(Fixtures.notImplemented),
|
|
24
|
-
verifyAuthenticationCredential: () => E.fail(Fixtures.notImplemented),
|
|
25
|
-
registerOidc: () => E.fail(Fixtures.notImplemented),
|
|
26
|
-
authenticateOidc: () => E.fail(Fixtures.notImplemented),
|
|
27
19
|
resendVerificationEmail: () => E.fail(Fixtures.notImplemented),
|
|
28
20
|
}),
|
|
29
21
|
)
|
package/src/user/user.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { UserClient } from '@passlock/shared/dist/rpc/user.js'
|
|
2
2
|
import { Effect as E, Layer as L, Layer, LogLevel, Logger, pipe } from 'effect'
|
|
3
3
|
import { describe, expect, test } from 'vitest'
|
|
4
4
|
import { mock } from 'vitest-mock-extended'
|
|
@@ -27,14 +27,14 @@ describe('isExistingUser should', () => {
|
|
|
27
27
|
const result = yield* _(service.isExistingUser({ email: Fixture.email }))
|
|
28
28
|
|
|
29
29
|
expect(result).toBe(false)
|
|
30
|
-
const rpcClient = yield* _(
|
|
30
|
+
const rpcClient = yield* _(UserClient)
|
|
31
31
|
expect(rpcClient.isExistingUser).toBeCalledWith(Fixture.isRegisteredReq)
|
|
32
32
|
})
|
|
33
33
|
|
|
34
34
|
const rpcClientTest = Layer.effect(
|
|
35
|
-
|
|
35
|
+
UserClient,
|
|
36
36
|
E.sync(() => {
|
|
37
|
-
const rpcMock = mock<
|
|
37
|
+
const rpcMock = mock<UserClient['Type']>()
|
|
38
38
|
|
|
39
39
|
rpcMock.isExistingUser.mockReturnValue(E.succeed(Fixture.isRegisteredRes))
|
|
40
40
|
|
|
@@ -57,14 +57,14 @@ describe('resendVerificationEmail should', () => {
|
|
|
57
57
|
const service = yield* _(UserService)
|
|
58
58
|
yield* _(service.resendVerificationEmail(Fixture.resendEmailReq))
|
|
59
59
|
|
|
60
|
-
const rpcClient = yield* _(
|
|
60
|
+
const rpcClient = yield* _(UserClient)
|
|
61
61
|
expect(rpcClient.resendVerificationEmail).toBeCalledWith(Fixture.rpcResendEmailReq)
|
|
62
62
|
})
|
|
63
63
|
|
|
64
64
|
const rpcClientTest = Layer.effect(
|
|
65
|
-
|
|
65
|
+
UserClient,
|
|
66
66
|
E.sync(() => {
|
|
67
|
-
const rpcMock = mock<
|
|
67
|
+
const rpcMock = mock<UserClient['Type']>()
|
|
68
68
|
|
|
69
69
|
rpcMock.resendVerificationEmail.mockReturnValue(E.succeed(Fixture.rpcResendEmailRes))
|
|
70
70
|
|
package/src/user/user.ts
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
* Check for an existing user
|
|
3
3
|
*/
|
|
4
4
|
import type { BadRequest, Disabled, NotFound } from '@passlock/shared/dist/error/error.js'
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import type { VerifyEmail } from '@passlock/shared/dist/schema/schema.js'
|
|
5
|
+
import { IsExistingUserReq, ResendEmailReq, UserClient } from '@passlock/shared/dist/rpc/user.js'
|
|
6
|
+
import type { VerifyEmail } from '@passlock/shared/dist/schema/email.js'
|
|
8
7
|
import { Context, Effect as E, Layer, flow } from 'effect'
|
|
9
8
|
|
|
10
9
|
/* Requests */
|
|
@@ -27,12 +26,12 @@ export const UserService = Context.GenericTag<UserService>('@services/UserServic
|
|
|
27
26
|
|
|
28
27
|
/* Effects */
|
|
29
28
|
|
|
30
|
-
type Dependencies =
|
|
29
|
+
type Dependencies = UserClient
|
|
31
30
|
|
|
32
31
|
export const isExistingUser = (request: Email): E.Effect<boolean, BadRequest, Dependencies> => {
|
|
33
32
|
return E.gen(function* (_) {
|
|
34
33
|
yield* _(E.logInfo('Checking registration status'))
|
|
35
|
-
const rpcClient = yield* _(
|
|
34
|
+
const rpcClient = yield* _(UserClient)
|
|
36
35
|
|
|
37
36
|
yield* _(E.logDebug('Making RPC request'))
|
|
38
37
|
const { existingUser } = yield* _(rpcClient.isExistingUser(new IsExistingUserReq(request)))
|
|
@@ -44,7 +43,7 @@ export const isExistingUser = (request: Email): E.Effect<boolean, BadRequest, De
|
|
|
44
43
|
export const resendVerificationEmail = (request: ResendEmail): E.Effect<void, ResendEmailErrors, Dependencies> => {
|
|
45
44
|
return E.gen(function* (_) {
|
|
46
45
|
yield* _(E.logInfo('Resending verification email'))
|
|
47
|
-
const rpcClient = yield* _(
|
|
46
|
+
const rpcClient = yield* _(UserClient)
|
|
48
47
|
|
|
49
48
|
yield* _(E.logDebug('Making RPC request'))
|
|
50
49
|
const { userId, ...verifyEmail } = request
|
|
@@ -58,7 +57,7 @@ export const resendVerificationEmail = (request: ResendEmail): E.Effect<void, Re
|
|
|
58
57
|
export const UserServiceLive = Layer.effect(
|
|
59
58
|
UserService,
|
|
60
59
|
E.gen(function* (_) {
|
|
61
|
-
const context = yield* _(E.context<
|
|
60
|
+
const context = yield* _(E.context<UserClient>())
|
|
62
61
|
return UserService.of({
|
|
63
62
|
isExistingUser: flow(isExistingUser, E.provide(context)),
|
|
64
63
|
resendVerificationEmail: flow(resendVerificationEmail, E.provide(context))
|
package/README.md
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
<a href="https://github.com/passlock-dev/passkeys-frontend">
|
|
3
|
-
<img src="https://github.com/passlock-dev/passkeys-frontend/assets/208345/53ee00d3-8e6c-49ea-b43c-3f901450c73b" alt="Passlock logo" width="80" height="80">
|
|
4
|
-
</a>
|
|
5
|
-
</div>
|
|
6
|
-
|
|
7
|
-
<a name="readme-top"></a>
|
|
8
|
-
<h1 align="center">Serverless Passkeys</h1>
|
|
9
|
-
|
|
10
|
-
<p align="center">
|
|
11
|
-
Passkey authentication for your web apps. Supports React, Angular, Vue, SvelteKit & others.
|
|
12
|
-
<br />
|
|
13
|
-
<a href="https://passlock.dev"><strong>Project website »</strong></a>
|
|
14
|
-
<br />
|
|
15
|
-
<a href="https://passlock.dev/#demo">Demo</a>
|
|
16
|
-
·
|
|
17
|
-
<a href="https://docs.passlock.dev">Documentation</a>
|
|
18
|
-
·
|
|
19
|
-
<a href="https://docs.passlock.dev/docs/tutorial/intro">Tutorial</a>
|
|
20
|
-
</p>
|
|
21
|
-
</div>
|
|
22
|
-
|
|
23
|
-
<br />
|
|
24
|
-
|
|
25
|
-
## Features
|
|
26
|
-
|
|
27
|
-
Passkeys and the WebAuthn API are quite complex. We've taken an opinionated approach to simplify things for you. Following the 80/20 principle we've tried to focus on the features most valuable to developers and users. We welcome feature requests so do [get in touch][contact].
|
|
28
|
-
|
|
29
|
-
1. **🔐 Primary & secondary authentication** - Replace password based logins with passkeys, or use passkeys alongside passwords for secondary authentication.
|
|
30
|
-
|
|
31
|
-
2. **☝🏻 Biometrics** - We've made it really easy to implement facial or fingerprint recognition in your webapps.
|
|
32
|
-
|
|
33
|
-
3. **🔐 Step up authentication** - Require biometric or PIN verification for some operations, whilst allowing one-tap authentication for others.
|
|
34
|
-
|
|
35
|
-
4. **🚀 Social login** - Quickly add social login to your web application.
|
|
36
|
-
|
|
37
|
-
5. **🖥️ Full management console** - Manage all security related aspects of your userbase through a web based console.
|
|
38
|
-
|
|
39
|
-
6. **🕵️ Audit trail** - View a full audit trail for each user: when they add a new passkey, when they login, verify their email address and much more.
|
|
40
|
-
|
|
41
|
-
## Screenshot
|
|
42
|
-
|
|
43
|
-

|
|
44
|
-
<p align="center">Viewing a user's authentication activity on their profile page</p>
|
|
45
|
-
|
|
46
|
-
## Usage
|
|
47
|
-
|
|
48
|
-
Use this library to generate a secure token, representing passkey registration or authentication. Send the token to your backend for verification (see below)
|
|
49
|
-
|
|
50
|
-
### Register a passkey
|
|
51
|
-
|
|
52
|
-
```typescript
|
|
53
|
-
import { Passlock, PasslockError } from '@passlock/client'
|
|
54
|
-
|
|
55
|
-
// you can find these details in the settings area of the Passlock console
|
|
56
|
-
const tenancyId = '...'
|
|
57
|
-
const clientId = '...'
|
|
58
|
-
|
|
59
|
-
const passlock = new Passlock({ tenancyId, clientId })
|
|
60
|
-
|
|
61
|
-
// to register a new passkey, call registerPasskey(). We're using placeholders for
|
|
62
|
-
// the user data. You should grab this from an HTML form, React store, Redux etc.
|
|
63
|
-
const [email, givenName, familyName] = ["jdoe@gmail.com", "John", "Doe"]
|
|
64
|
-
|
|
65
|
-
// Passlock doesn't throw but instead returns a union: result | error
|
|
66
|
-
const result = await passlock.registerPasskey({ email, givenName, familyName })
|
|
67
|
-
|
|
68
|
-
// ensure Passlock didn't return an error
|
|
69
|
-
if (!PasslockError.isError(result)) {
|
|
70
|
-
// send the token to your backend (json/fetch or hidden form field etc)
|
|
71
|
-
console.log('Token: %s', result.token)
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### Authenticate using a passkey
|
|
76
|
-
|
|
77
|
-
```typescript
|
|
78
|
-
import { Passlock, PasslockError } from '@passlock/client'
|
|
79
|
-
|
|
80
|
-
const tenancyId = '...'
|
|
81
|
-
const clientId = '...'
|
|
82
|
-
|
|
83
|
-
const passlock = new Passlock({ tenancyId, clientId })
|
|
84
|
-
const result = await passlock.authenticatePasskey()
|
|
85
|
-
|
|
86
|
-
if (!PasslockError.isError(result)) {
|
|
87
|
-
// send the token to your backend for verification
|
|
88
|
-
console.log('Token: %s', result.token)
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### Backend verification
|
|
93
|
-
|
|
94
|
-
Verify the token and obtain the passkey registration or authentication details. You can make a simple GET request to `https://api.passlock.dev/{tenancyId}/token/{token}` or use the [@passlock/node][node] library:
|
|
95
|
-
|
|
96
|
-
```typescript
|
|
97
|
-
import { Passlock } from '@passlock/node'
|
|
98
|
-
|
|
99
|
-
// API Keys can be found in your passlock console
|
|
100
|
-
const passlock = new Passlock({ tenancyId, apiKey })
|
|
101
|
-
|
|
102
|
-
// token comes from your frontend
|
|
103
|
-
const principal = await passlock.fetchPrincipal({ token })
|
|
104
|
-
|
|
105
|
-
// get the user id
|
|
106
|
-
console.log(principal.user.id)
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
## More information
|
|
110
|
-
|
|
111
|
-
Please see the [tutorial][tutorial] and [documentation][docs]
|
|
112
|
-
|
|
113
|
-
[contact]: https://passlock.dev/contact
|
|
114
|
-
[tutorial]: https://docs.passlock.dev/docs/tutorial/intro
|
|
115
|
-
[docs]: https://docs.passlock.dev
|
|
116
|
-
[node]: https://www.npmjs.com/package/@passlock/node
|