@structured-id/ui-core 4.12.0 → 4.13.0
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/package.json +3 -2
- package/src/profile/components/SidLoginForm.vue +3 -2
- package/src/profile/composables/useAuth.test.ts +37 -34
- package/src/profile/composables/useAuth.ts +9 -9
- package/src/profile/composables/useAuthApi.ids.test.ts +248 -0
- package/src/profile/composables/useAuthApi.opaque.test.ts +55 -93
- package/src/profile/composables/useAuthApi.ts +189 -92
- package/src/profile/composables/useLoginSuccess.ts +2 -1
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from "
|
|
3
|
+
IdError,
|
|
4
|
+
MissingIdError,
|
|
5
|
+
OpaqueLoginId,
|
|
6
|
+
SessionId,
|
|
7
|
+
} from "@structured-id/ids";
|
|
8
|
+
import { opaqueLoginStart, opaqueLoginFinish } from "./useAuthApi";
|
|
8
9
|
|
|
9
10
|
// Mock the gRPC transport + AuthServiceClient
|
|
10
11
|
const mockOpaqueLoginStart = vi.fn();
|
|
11
12
|
const mockOpaqueLoginFinish = vi.fn();
|
|
12
|
-
const mockOpaqueRegistrationStart = vi.fn();
|
|
13
|
-
const mockOpaqueRegistrationFinish = vi.fn();
|
|
14
13
|
|
|
15
14
|
vi.mock("../../index", () => ({
|
|
16
15
|
getTransport: vi.fn(),
|
|
@@ -23,8 +22,6 @@ vi.mock("@structured-id/proto/sid/v1/authn/auth.client", () => ({
|
|
|
23
22
|
return {
|
|
24
23
|
opaqueLoginStart: mockOpaqueLoginStart,
|
|
25
24
|
opaqueLoginFinish: mockOpaqueLoginFinish,
|
|
26
|
-
opaqueRegistrationStart: mockOpaqueRegistrationStart,
|
|
27
|
-
opaqueRegistrationFinish: mockOpaqueRegistrationFinish,
|
|
28
25
|
};
|
|
29
26
|
}),
|
|
30
27
|
}));
|
|
@@ -34,18 +31,23 @@ vi.mock("../auth", () => ({
|
|
|
34
31
|
authMeta: () => ({}),
|
|
35
32
|
}));
|
|
36
33
|
|
|
34
|
+
const LOGIN = OpaqueLoginId.parse("0192f3a4-7c1e-7b2a-9d4e-3f5a6b7c8d9e");
|
|
35
|
+
const SESSION = SessionId.parse("0192f3a4-7c1e-7b2a-9d4e-3f5a6b7c8d9f");
|
|
36
|
+
|
|
37
37
|
describe("OPAQUE gRPC wrappers", () => {
|
|
38
38
|
beforeEach(() => {
|
|
39
39
|
vi.clearAllMocks();
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
describe("opaqueLoginStart", () => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
// The start names its login; the finish carries that name back.
|
|
44
|
+
it("sends principal and credentialRequest, reads the login it began", async () => {
|
|
45
|
+
mockOpaqueLoginStart.mockResolvedValue({
|
|
46
|
+
response: {
|
|
47
|
+
credentialResponse: new Uint8Array([1, 2, 3]),
|
|
48
|
+
loginId: LOGIN.toWire(),
|
|
49
|
+
},
|
|
50
|
+
});
|
|
49
51
|
|
|
50
52
|
const result = await opaqueLoginStart(
|
|
51
53
|
"alice@test.com",
|
|
@@ -56,7 +58,29 @@ describe("OPAQUE gRPC wrappers", () => {
|
|
|
56
58
|
principal: "alice@test.com",
|
|
57
59
|
credentialRequest: new Uint8Array([10, 20]),
|
|
58
60
|
});
|
|
59
|
-
expect(result).
|
|
61
|
+
expect(result.credentialResponse).toEqual(new Uint8Array([1, 2, 3]));
|
|
62
|
+
expect(result.loginId.equals(LOGIN)).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// A start that names no login, or a malformed one, cannot be finished:
|
|
66
|
+
// it fails here rather than sending an unusable finish.
|
|
67
|
+
it("refuses an answer without a valid login", async () => {
|
|
68
|
+
mockOpaqueLoginStart.mockResolvedValue({
|
|
69
|
+
response: { credentialResponse: new Uint8Array([1]) },
|
|
70
|
+
});
|
|
71
|
+
await expect(
|
|
72
|
+
opaqueLoginStart("x", new Uint8Array()),
|
|
73
|
+
).rejects.toBeInstanceOf(MissingIdError);
|
|
74
|
+
|
|
75
|
+
mockOpaqueLoginStart.mockResolvedValue({
|
|
76
|
+
response: {
|
|
77
|
+
credentialResponse: new Uint8Array([1]),
|
|
78
|
+
loginId: { value: new Uint8Array(15) },
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
await expect(
|
|
82
|
+
opaqueLoginStart("x", new Uint8Array()),
|
|
83
|
+
).rejects.toBeInstanceOf(IdError);
|
|
60
84
|
});
|
|
61
85
|
|
|
62
86
|
it("propagates gRPC errors", async () => {
|
|
@@ -68,97 +92,35 @@ describe("OPAQUE gRPC wrappers", () => {
|
|
|
68
92
|
});
|
|
69
93
|
|
|
70
94
|
describe("opaqueLoginFinish", () => {
|
|
71
|
-
it("sends principal, finalization
|
|
72
|
-
|
|
73
|
-
sessionId:
|
|
74
|
-
|
|
75
|
-
};
|
|
76
|
-
mockOpaqueLoginFinish.mockResolvedValue({ response: expected });
|
|
95
|
+
it("sends principal, finalization and the login's bytes, reads the session", async () => {
|
|
96
|
+
mockOpaqueLoginFinish.mockResolvedValue({
|
|
97
|
+
response: { sessionId: SESSION.toWire(), expiresIn: BigInt(3600) },
|
|
98
|
+
});
|
|
77
99
|
|
|
78
100
|
const result = await opaqueLoginFinish(
|
|
79
101
|
"alice@test.com",
|
|
80
102
|
new Uint8Array([7, 8, 9]),
|
|
81
|
-
|
|
103
|
+
LOGIN,
|
|
82
104
|
);
|
|
83
105
|
|
|
84
106
|
expect(mockOpaqueLoginFinish).toHaveBeenCalledWith({
|
|
85
107
|
principal: "alice@test.com",
|
|
86
108
|
credentialFinalization: new Uint8Array([7, 8, 9]),
|
|
87
|
-
|
|
88
|
-
});
|
|
89
|
-
expect(result).toBe(expected);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
describe("opaqueRegistrationStart", () => {
|
|
94
|
-
it("sends principal and registrationRequest", async () => {
|
|
95
|
-
const expected = {
|
|
96
|
-
registrationResponse: new Uint8Array([4, 5, 6]),
|
|
97
|
-
serverSetup: "setup-abc",
|
|
98
|
-
};
|
|
99
|
-
mockOpaqueRegistrationStart.mockResolvedValue({ response: expected });
|
|
100
|
-
|
|
101
|
-
const result = await opaqueRegistrationStart(
|
|
102
|
-
"bob@test.com",
|
|
103
|
-
new Uint8Array([11, 12]),
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
expect(mockOpaqueRegistrationStart).toHaveBeenCalledWith({
|
|
107
|
-
principal: "bob@test.com",
|
|
108
|
-
registrationRequest: new Uint8Array([11, 12]),
|
|
109
|
-
});
|
|
110
|
-
expect(result).toBe(expected);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// An unclaimed installation accepts only a registration carrying the
|
|
114
|
-
// claim token; the wrapper must put it on the wire unchanged.
|
|
115
|
-
it("sends the instance claim token when given", async () => {
|
|
116
|
-
mockOpaqueRegistrationStart.mockResolvedValue({ response: {} });
|
|
117
|
-
|
|
118
|
-
await opaqueRegistrationStart(
|
|
119
|
-
"owner@test.com",
|
|
120
|
-
new Uint8Array([1]),
|
|
121
|
-
"sidclaim_abc",
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
expect(mockOpaqueRegistrationStart).toHaveBeenCalledWith({
|
|
125
|
-
principal: "owner@test.com",
|
|
126
|
-
registrationRequest: new Uint8Array([1]),
|
|
127
|
-
claimToken: "sidclaim_abc",
|
|
109
|
+
loginId: { value: LOGIN.toBytes() },
|
|
128
110
|
});
|
|
111
|
+
expect(result.sessionId.equals(SESSION)).toBe(true);
|
|
112
|
+
expect(result.expiresIn).toBe(BigInt(3600));
|
|
129
113
|
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
describe("opaqueRegistrationFinish", () => {
|
|
133
|
-
it("sends principal, record, and serverSetup", async () => {
|
|
134
|
-
const expected = {
|
|
135
|
-
profileId: "profile-uuid",
|
|
136
|
-
credentialId: "cred-uuid",
|
|
137
|
-
serverPepper: new Uint8Array(),
|
|
138
|
-
};
|
|
139
|
-
mockOpaqueRegistrationFinish.mockResolvedValue({ response: expected });
|
|
140
|
-
|
|
141
|
-
const result = await opaqueRegistrationFinish(
|
|
142
|
-
"bob@test.com",
|
|
143
|
-
new Uint8Array([20, 21]),
|
|
144
|
-
"setup-abc",
|
|
145
|
-
);
|
|
146
114
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
115
|
+
// Every completed sign-in names its session; an answer without one is
|
|
116
|
+
// not a sign-in the page can manage.
|
|
117
|
+
it("refuses an answer without its session", async () => {
|
|
118
|
+
mockOpaqueLoginFinish.mockResolvedValue({
|
|
119
|
+
response: { expiresIn: BigInt(3600) },
|
|
151
120
|
});
|
|
152
|
-
expect(result).toBe(expected);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it("propagates gRPC errors", async () => {
|
|
156
|
-
mockOpaqueRegistrationFinish.mockRejectedValue(
|
|
157
|
-
new Error("ALREADY_EXISTS"),
|
|
158
|
-
);
|
|
159
121
|
await expect(
|
|
160
|
-
|
|
161
|
-
).rejects.
|
|
122
|
+
opaqueLoginFinish("x", new Uint8Array(), LOGIN),
|
|
123
|
+
).rejects.toBeInstanceOf(MissingIdError);
|
|
162
124
|
});
|
|
163
125
|
});
|
|
164
126
|
});
|