@crossauth/sveltekit 1.0.1 → 1.1.1
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 +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +16 -6181
- package/dist/sveltekitadminclientendpoints.d.ts +13 -12
- package/dist/sveltekitadminclientendpoints.js +187 -0
- package/dist/sveltekitadminendpoints.d.ts +5 -4
- package/dist/sveltekitadminendpoints.js +766 -0
- package/dist/sveltekitapikey.d.ts +4 -3
- package/dist/sveltekitapikey.js +81 -0
- package/dist/sveltekitoauthclient.d.ts +6 -4
- package/dist/sveltekitoauthclient.js +2309 -0
- package/dist/sveltekitoauthserver.d.ts +4 -4
- package/dist/sveltekitoauthserver.js +1350 -0
- package/dist/sveltekitresserver.d.ts +6 -4
- package/dist/sveltekitresserver.js +286 -0
- package/dist/sveltekitserver.d.ts +11 -9
- package/dist/sveltekitserver.js +393 -0
- package/dist/sveltekitsession.d.ts +6 -5
- package/dist/sveltekitsession.js +1112 -0
- package/dist/sveltekitsessionadapter.d.ts +2 -3
- package/dist/sveltekitsessionadapter.js +2 -0
- package/dist/sveltekitsharedclientendpoints.d.ts +7 -6
- package/dist/sveltekitsharedclientendpoints.js +630 -0
- package/dist/sveltekituserclientendpoints.d.ts +13 -12
- package/dist/sveltekituserclientendpoints.js +270 -0
- package/dist/sveltekituserendpoints.d.ts +6 -5
- package/dist/sveltekituserendpoints.js +1813 -0
- package/dist/tests/sveltekitadminclientendpoints.test.js +330 -0
- package/dist/tests/sveltekitadminendpoints.test.js +242 -0
- package/dist/tests/sveltekitapikeyserver.test.js +44 -0
- package/dist/tests/sveltekitoauthclient.test.d.ts +5 -5
- package/dist/tests/sveltekitoauthclient.test.js +1016 -0
- package/dist/tests/sveltekitoauthresserver.test.d.ts +4 -4
- package/dist/tests/sveltekitoauthresserver.test.js +185 -0
- package/dist/tests/sveltekitoauthserver.test.js +673 -0
- package/dist/tests/sveltekituserclientendpoints.test.js +244 -0
- package/dist/tests/sveltekituserendpoints.test.js +152 -0
- package/dist/tests/sveltemock.test.js +36 -0
- package/dist/tests/sveltemocks.d.ts +22 -8
- package/dist/tests/sveltemocks.js +114 -0
- package/dist/tests/sveltesessionhooks.test.js +224 -0
- package/dist/tests/testshared.d.ts +8 -8
- package/dist/tests/testshared.js +344 -0
- package/dist/utils.d.ts +1 -2
- package/dist/utils.js +123 -0
- package/package.json +23 -15
- package/dist/index.cjs +0 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// Copyright (c) 2026 Matthew Baker. All rights reserved. Licenced under the Apache Licence 2.0. See LICENSE file
|
|
2
|
+
import { MockRequestEvent } from './sveltemocks';
|
|
3
|
+
import { JsonOrFormData } from '../utils';
|
|
4
|
+
import { test, expect } from 'vitest';
|
|
5
|
+
import { createSession, makeServer, getCookies, login, loginFactor2, getCsrfToken } from './testshared';
|
|
6
|
+
test('SvelteSessionHooks.hookWithGetNotLoggedIn', async () => {
|
|
7
|
+
const { server, resolver, handle } = await makeServer();
|
|
8
|
+
const getRequest = new Request("http://ex.com/test", { method: "GET" });
|
|
9
|
+
let event = new MockRequestEvent("1", getRequest, { "param1": "value1" });
|
|
10
|
+
const resp = await handle({ event: event, resolve: resolver.mockResolve });
|
|
11
|
+
const cookies = getCookies(resp);
|
|
12
|
+
expect(cookies["CSRFTOKEN"]).toBeDefined();
|
|
13
|
+
let csrfValid = false;
|
|
14
|
+
try {
|
|
15
|
+
server.sessionServer?.sessionManager.validateCsrfCookie(cookies["CSRFTOKEN"]);
|
|
16
|
+
csrfValid = true;
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
console.log(e);
|
|
20
|
+
}
|
|
21
|
+
expect(csrfValid).toBe(true);
|
|
22
|
+
expect(event.locals.csrfToken).toBeDefined();
|
|
23
|
+
csrfValid = false;
|
|
24
|
+
try {
|
|
25
|
+
server.sessionServer?.sessionManager.validateDoubleSubmitCsrfToken(cookies["CSRFTOKEN"], event.locals.csrfToken);
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
console.log(e);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
test('SvelteSessionHooks.hookWithPostNotLoggedIn', async () => {
|
|
32
|
+
const { resolver, handle } = await makeServer();
|
|
33
|
+
const postRequest = new Request("http://ex.com/test", { method: "POST", body: "This is the body" });
|
|
34
|
+
let event = new MockRequestEvent("1", postRequest, { "param1": "value1" });
|
|
35
|
+
const resp = await handle({ event: event, resolve: resolver.mockResolve });
|
|
36
|
+
const cookies = getCookies(resp);
|
|
37
|
+
expect(cookies["CSRFTOKEN"]).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
test('SvelteMocks.hookGetThenPost', async () => {
|
|
40
|
+
const { server, resolver, handle } = await makeServer();
|
|
41
|
+
const getRequest = new Request("http://ex.com/test", { method: "GET" });
|
|
42
|
+
let event = new MockRequestEvent("1", getRequest, { "param1": "value1" });
|
|
43
|
+
let resp = await handle({ event: event, resolve: resolver.mockResolve });
|
|
44
|
+
const cookies = getCookies(resp);
|
|
45
|
+
expect(cookies["CSRFTOKEN"]).toBeDefined();
|
|
46
|
+
const postRequest = new Request("http://ex.com/test", {
|
|
47
|
+
method: "POST",
|
|
48
|
+
body: "csrfToken=" + event.locals.csrfToken,
|
|
49
|
+
headers: {
|
|
50
|
+
"cookie": "CSRFTOKEN=" + cookies["CSRFTOKEN"],
|
|
51
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
event = new MockRequestEvent("1", postRequest, { "param1": "value1" });
|
|
55
|
+
resp = await handle({ event: event, resolve: resolver.mockResolve });
|
|
56
|
+
let csrfValid = false;
|
|
57
|
+
try {
|
|
58
|
+
server.sessionServer?.sessionManager.validateCsrfCookie(cookies["CSRFTOKEN"]);
|
|
59
|
+
csrfValid = true;
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
console.log(e);
|
|
63
|
+
}
|
|
64
|
+
expect(csrfValid).toBe(true);
|
|
65
|
+
expect(event.locals.csrfToken).toBeDefined();
|
|
66
|
+
csrfValid = false;
|
|
67
|
+
try {
|
|
68
|
+
server.sessionServer?.sessionManager.validateDoubleSubmitCsrfToken(cookies["CSRFTOKEN"], event.locals.csrfToken);
|
|
69
|
+
csrfValid = true;
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
console.log(e);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
test('SvelteSessionHooks.formBody', async () => {
|
|
76
|
+
const postRequest = new Request("http://ex.com/test", {
|
|
77
|
+
method: "POST",
|
|
78
|
+
body: "param1=value1¶m%262=value+2",
|
|
79
|
+
headers: {
|
|
80
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
const event = new MockRequestEvent("1", postRequest, {});
|
|
84
|
+
const data = new JsonOrFormData();
|
|
85
|
+
await data.loadData(event);
|
|
86
|
+
let keys = [...data.keys()];
|
|
87
|
+
expect(keys.length).toBe(2);
|
|
88
|
+
expect(["param1", "param&2"]).toContain(keys[0]);
|
|
89
|
+
expect(["param1", "param&2"]).toContain(keys[1]);
|
|
90
|
+
expect(data.get("param1")).toBe("value1");
|
|
91
|
+
expect(data.get("param&2")).toBe("value 2");
|
|
92
|
+
expect(data.get("X")).toBeUndefined();
|
|
93
|
+
const obj = data.toObject();
|
|
94
|
+
expect(obj["param1"]).toBe("value1");
|
|
95
|
+
});
|
|
96
|
+
test('SvelteSessionHooks.jsonBody', async () => {
|
|
97
|
+
const body = JSON.stringify({ "param1": "value1", "param&2": "value 2" });
|
|
98
|
+
const postRequest = new Request("http://ex.com/test", {
|
|
99
|
+
method: "POST",
|
|
100
|
+
body: body,
|
|
101
|
+
headers: {
|
|
102
|
+
"content-type": "application/json",
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
const event = new MockRequestEvent("1", postRequest, {});
|
|
106
|
+
const data = new JsonOrFormData();
|
|
107
|
+
await data.loadData(event);
|
|
108
|
+
let keys = [...data.keys()];
|
|
109
|
+
expect(keys.length).toBe(2);
|
|
110
|
+
expect(["param1", "param&2"]).toContain(keys[0]);
|
|
111
|
+
expect(["param1", "param&2"]).toContain(keys[1]);
|
|
112
|
+
expect(data.get("param1")).toBe("value1");
|
|
113
|
+
expect(data.get("param&2")).toBe("value 2");
|
|
114
|
+
expect(data.get("X")).toBeUndefined();
|
|
115
|
+
});
|
|
116
|
+
test('SvelteSessionHooks.hookWithGetIsLoggedIn', async () => {
|
|
117
|
+
const { server, resolver, handle, userStorage, keyStorage } = await makeServer();
|
|
118
|
+
const sessionKey = await createSession("bob", userStorage, keyStorage);
|
|
119
|
+
const getRequest = new Request("http://ex.com/test", {
|
|
120
|
+
method: "GET", headers: {
|
|
121
|
+
"cookie": sessionKey.cookie.name + "=" + sessionKey.cookie.value,
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
let event = new MockRequestEvent("1", getRequest, { "param1": "value1" });
|
|
125
|
+
const resp = await handle({ event: event, resolve: resolver.mockResolve });
|
|
126
|
+
const cookies = getCookies(resp);
|
|
127
|
+
expect(cookies["CSRFTOKEN"]).toBeDefined();
|
|
128
|
+
let csrfValid = false;
|
|
129
|
+
try {
|
|
130
|
+
server.sessionServer?.sessionManager.validateCsrfCookie(cookies["CSRFTOKEN"]);
|
|
131
|
+
csrfValid = true;
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
console.log(e);
|
|
135
|
+
}
|
|
136
|
+
expect(csrfValid).toBe(true);
|
|
137
|
+
expect(event.locals.csrfToken).toBeDefined();
|
|
138
|
+
expect(event.locals.user).toBeDefined();
|
|
139
|
+
expect(event.locals.user?.username).toBe("bob");
|
|
140
|
+
csrfValid = false;
|
|
141
|
+
try {
|
|
142
|
+
server.sessionServer?.sessionManager.validateDoubleSubmitCsrfToken(cookies["CSRFTOKEN"], event.locals.csrfToken);
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
console.log(e);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
test('SvelteSessionHooks.loginProtectedNotLoggedIn', async () => {
|
|
149
|
+
const { server, resolver, handle } = await makeServer();
|
|
150
|
+
let getRequest = new Request("http://ex.com/account", { method: "GET" });
|
|
151
|
+
let event = new MockRequestEvent("1", getRequest, { "param1": "value1" });
|
|
152
|
+
let resp = await handle({ event: event, resolve: resolver.mockResolve });
|
|
153
|
+
expect(resp.status).toBe(302);
|
|
154
|
+
expect(resp.headers.get('location')).toContain("/login");
|
|
155
|
+
// log in
|
|
156
|
+
let { event: loginEvent } = await login(server, resolver, handle);
|
|
157
|
+
// try again now that we are logged in
|
|
158
|
+
event.request = getRequest;
|
|
159
|
+
resp = await handle({ event: loginEvent, resolve: resolver.mockResolve });
|
|
160
|
+
expect(resp.status).toBe(200);
|
|
161
|
+
});
|
|
162
|
+
test('SvelteSessionHooks.login2FA', async () => {
|
|
163
|
+
const { server, resolver, handle } = await makeServer();
|
|
164
|
+
// log in
|
|
165
|
+
let resp = await login(server, resolver, handle, "alice", "alicePass123");
|
|
166
|
+
const loginEvent = resp.event;
|
|
167
|
+
const sessionCookie = loginEvent.cookies.get("SESSIONID");
|
|
168
|
+
let ret = resp.ret;
|
|
169
|
+
expect(ret?.factor2Required).toBe(true);
|
|
170
|
+
const sessionId = server.sessionServer?.sessionManager.getSessionId(sessionCookie ?? "");
|
|
171
|
+
resp = await loginFactor2(server, resolver, handle, sessionCookie ?? "", sessionId ?? "");
|
|
172
|
+
ret = resp.ret;
|
|
173
|
+
expect(ret?.ok).toBe(true);
|
|
174
|
+
expect(ret?.user?.username).toBe("alice");
|
|
175
|
+
});
|
|
176
|
+
test('SvelteSessionHooks.visitPage2FA', async () => {
|
|
177
|
+
const { server, resolver, handle } = await makeServer();
|
|
178
|
+
// log in
|
|
179
|
+
let resp = await login(server, resolver, handle, "alice", "alicePass123");
|
|
180
|
+
let loginEvent = resp.event;
|
|
181
|
+
let sessionCookieValue = loginEvent.cookies.get("SESSIONID");
|
|
182
|
+
let ret = resp.ret;
|
|
183
|
+
expect(ret?.factor2Required).toBe(true);
|
|
184
|
+
let sessionId = server.sessionServer?.sessionManager.getSessionId(sessionCookieValue ?? "");
|
|
185
|
+
resp = await loginFactor2(server, resolver, handle, sessionCookieValue ?? "", sessionId ?? "");
|
|
186
|
+
loginEvent = resp.event;
|
|
187
|
+
ret = resp.ret;
|
|
188
|
+
expect(ret?.ok).toBe(true);
|
|
189
|
+
expect(ret?.user?.username).toBe("alice");
|
|
190
|
+
const { csrfToken, csrfCookieValue } = await getCsrfToken(server, resolver, handle);
|
|
191
|
+
// visit factor2-protected page
|
|
192
|
+
let postRequest = new Request("http://ex.com/factor2protected", {
|
|
193
|
+
method: "POST",
|
|
194
|
+
body: "csrfToken=" + csrfToken + "¶m1=value1",
|
|
195
|
+
headers: {
|
|
196
|
+
"cookie": "CSRFTOKEN=" + csrfCookieValue,
|
|
197
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
let event = new MockRequestEvent("1", postRequest, {});
|
|
201
|
+
event.locals.csrfToken = csrfToken;
|
|
202
|
+
event.locals.user = ret?.user;
|
|
203
|
+
sessionCookieValue = loginEvent.cookies.get("SESSIONID");
|
|
204
|
+
event.cookies.set("SESSIONID", sessionCookieValue ?? "", { path: "/" });
|
|
205
|
+
//sessionId = loginEvent.locals.sessionId;
|
|
206
|
+
sessionId = server.sessionServer?.sessionManager.getSessionId(sessionCookieValue ?? "");
|
|
207
|
+
event.locals.sessionId = sessionId;
|
|
208
|
+
let resp1 = await handle({ event: event, resolve: resolver.mockResolve });
|
|
209
|
+
expect(resp1.status).toBe(302);
|
|
210
|
+
expect(resp1.headers.get("location")).toBe("/factor2/");
|
|
211
|
+
// submit factor2
|
|
212
|
+
postRequest = new Request("http://ex.com/factor2protected", {
|
|
213
|
+
method: "POST",
|
|
214
|
+
body: "csrfToken=" + csrfToken + "&otp=0000",
|
|
215
|
+
headers: [
|
|
216
|
+
["cookie", "CSRFTOKEN=" + csrfCookieValue],
|
|
217
|
+
["cookie", "SESSIONID=" + sessionCookieValue],
|
|
218
|
+
["content-type", "application/x-www-form-urlencoded"],
|
|
219
|
+
]
|
|
220
|
+
});
|
|
221
|
+
event = new MockRequestEvent("1", postRequest, {});
|
|
222
|
+
resp1 = await handle({ event: event, resolve: resolver.mockResolve });
|
|
223
|
+
expect(resp1.status).toBe(200);
|
|
224
|
+
});
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { MockResolver, MockRequestEvent } from './sveltemocks';
|
|
2
2
|
import { SvelteKitServer } from '../sveltekitserver';
|
|
3
|
-
import { OAuthAuthorizationServer
|
|
4
|
-
import { OpenIdConfiguration, OAuthClient } from '@crossauth/common';
|
|
5
|
-
import {
|
|
6
|
-
|
|
3
|
+
import { OAuthAuthorizationServer } from '@crossauth/backend';
|
|
4
|
+
import { type OpenIdConfiguration, type OAuthClient } from '@crossauth/common';
|
|
5
|
+
import { InMemoryKeyStorage, InMemoryUserStorage, InMemoryOAuthClientStorage, LocalPasswordAuthenticator, ApiKeyManager } from '@crossauth/backend';
|
|
6
|
+
import type { Handle } from '@sveltejs/kit';
|
|
7
7
|
export declare const oidcConfiguration: OpenIdConfiguration;
|
|
8
8
|
export declare function createUsers(userStorage: InMemoryUserStorage): Promise<void>;
|
|
9
9
|
export declare function createSession(userid: string, userStorage: InMemoryUserStorage, keyStorage: InMemoryKeyStorage, options?: {}): Promise<{
|
|
10
|
-
key: import(
|
|
11
|
-
cookie: import(
|
|
10
|
+
key: import("@crossauth/common").Key;
|
|
11
|
+
cookie: import("@crossauth/backend").Cookie;
|
|
12
12
|
}>;
|
|
13
13
|
export declare function createClients(clientStorage: InMemoryOAuthClientStorage, secretRequired?: boolean): Promise<OAuthClient>;
|
|
14
14
|
export declare function makeServer(makeSession?: boolean, makeApiKey?: boolean, makeOAuthServer?: boolean, makeOAuthClient?: boolean, options?: {}): Promise<{
|
|
@@ -32,13 +32,13 @@ export declare function login(server: SvelteKitServer, resolver: MockResolver, h
|
|
|
32
32
|
event: MockRequestEvent<{
|
|
33
33
|
param1: string;
|
|
34
34
|
}, "1">;
|
|
35
|
-
ret: import(
|
|
35
|
+
ret: import("..").LoginReturn | undefined;
|
|
36
36
|
}>;
|
|
37
37
|
export declare function loginFactor2(server: SvelteKitServer, resolver: MockResolver, handle: Handle, sessionCookieValue: string, sessionId: string): Promise<{
|
|
38
38
|
event: MockRequestEvent<{
|
|
39
39
|
param1: string;
|
|
40
40
|
}, "1">;
|
|
41
|
-
ret: import(
|
|
41
|
+
ret: import("..").LoginReturn | undefined;
|
|
42
42
|
}>;
|
|
43
43
|
export declare function getAuthServer({ aud, persistAccessToken, emptyScopeIsValid, secretRequired, rollingRefreshToken, }?: {
|
|
44
44
|
challenge?: boolean;
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
// Copyright (c) 2026 Matthew Baker. All rights reserved. Licenced under the Apache Licence 2.0. See LICENSE file
|
|
2
|
+
import { MockResolver, MockRequestEvent } from './sveltemocks';
|
|
3
|
+
import { SvelteKitServer } from '../sveltekitserver';
|
|
4
|
+
import { OAuthAuthorizationServer } from '@crossauth/backend';
|
|
5
|
+
import {} from '@crossauth/common';
|
|
6
|
+
import { test, expect } from 'vitest';
|
|
7
|
+
import { InMemoryKeyStorage, InMemoryUserStorage, InMemoryOAuthClientStorage, InMemoryOAuthAuthorizationStorage, LocalPasswordAuthenticator, Crypto, DummyFactor2Authenticator, SessionCookie, EmailAuthenticator, ApiKeyManager } from '@crossauth/backend';
|
|
8
|
+
import { OAuthFlows } from '@crossauth/common';
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
export const oidcConfiguration = {
|
|
11
|
+
issuer: "http://server.com",
|
|
12
|
+
authorization_endpoint: "http://server.com/authorize",
|
|
13
|
+
token_endpoint: "http://server.com/token",
|
|
14
|
+
token_endpoint_auth_methods_supported: ["client_secret_post"],
|
|
15
|
+
jwks_uri: "http://server.com/jwks",
|
|
16
|
+
response_types_supported: ["code"],
|
|
17
|
+
response_modes_supported: ["query"],
|
|
18
|
+
grant_types_supported: ["authorization_code", "client_credentials", "password", "refresh_token", "http://auth0.com/oauth/grant-type/mfa-otp", "http://auth0.com/oauth/grant-type/mfa-oob", "urn:ietf:params:oauth:grant-type:device_code"],
|
|
19
|
+
token_endpoint_auth_signing_alg_values_supported: ["RS256"],
|
|
20
|
+
subject_types_supported: ["public"],
|
|
21
|
+
id_token_signing_alg_values_supported: ["RS256"],
|
|
22
|
+
claims_supported: ["iss", "sub", "aud", "jti", "iat", "type"],
|
|
23
|
+
request_uri_parameter_supported: true,
|
|
24
|
+
require_request_uri_registration: true,
|
|
25
|
+
};
|
|
26
|
+
export async function createUsers(userStorage) {
|
|
27
|
+
let authenticator = new LocalPasswordAuthenticator(userStorage, { pbkdf2Iterations: 1_000 });
|
|
28
|
+
await Promise.all([
|
|
29
|
+
userStorage.createUser({
|
|
30
|
+
username: "bob",
|
|
31
|
+
email: "bob@bob.com",
|
|
32
|
+
state: "active",
|
|
33
|
+
factor1: "localpassword"
|
|
34
|
+
}, {
|
|
35
|
+
password: await authenticator.createPasswordHash("bobPass123")
|
|
36
|
+
}),
|
|
37
|
+
userStorage.createUser({
|
|
38
|
+
username: "alice",
|
|
39
|
+
email: "alice@alice.com",
|
|
40
|
+
state: "active",
|
|
41
|
+
factor1: "localpassword",
|
|
42
|
+
factor2: "dummyFactor2"
|
|
43
|
+
}, {
|
|
44
|
+
password: await authenticator.createPasswordHash("alicePass123")
|
|
45
|
+
}),
|
|
46
|
+
userStorage.createUser({
|
|
47
|
+
username: "admin",
|
|
48
|
+
email: "admin@admin.com",
|
|
49
|
+
state: "active",
|
|
50
|
+
factor1: "localpassword",
|
|
51
|
+
admin: true
|
|
52
|
+
}, {
|
|
53
|
+
password: await authenticator.createPasswordHash("adminPass123")
|
|
54
|
+
}),
|
|
55
|
+
]);
|
|
56
|
+
}
|
|
57
|
+
export async function createSession(userid, userStorage, keyStorage, options = {}) {
|
|
58
|
+
let sessionCookie = new SessionCookie(keyStorage, { userStorage, ...options });
|
|
59
|
+
const key = await sessionCookie.createSessionKey(userid);
|
|
60
|
+
const cookie = sessionCookie.makeCookie(key);
|
|
61
|
+
return { key, cookie };
|
|
62
|
+
}
|
|
63
|
+
export async function createClients(clientStorage, secretRequired = true) {
|
|
64
|
+
const client_secret = await Crypto.passwordHash("DEF", {
|
|
65
|
+
encode: true,
|
|
66
|
+
iterations: 1000,
|
|
67
|
+
keyLen: 32,
|
|
68
|
+
});
|
|
69
|
+
let client = {
|
|
70
|
+
client_id: "ABC",
|
|
71
|
+
client_secret: secretRequired ? client_secret : undefined,
|
|
72
|
+
client_name: "Test",
|
|
73
|
+
confidential: secretRequired,
|
|
74
|
+
redirect_uri: ["http://example.com/redirect"],
|
|
75
|
+
valid_flow: OAuthFlows.allFlows(),
|
|
76
|
+
};
|
|
77
|
+
const retClient = client;
|
|
78
|
+
await clientStorage.createClient(client);
|
|
79
|
+
client = {
|
|
80
|
+
client_id: "bob_ABC",
|
|
81
|
+
client_secret: secretRequired ? client_secret : undefined,
|
|
82
|
+
client_name: "Test",
|
|
83
|
+
confidential: secretRequired,
|
|
84
|
+
redirect_uri: ["http://example.com/redirect"],
|
|
85
|
+
valid_flow: OAuthFlows.allFlows(),
|
|
86
|
+
userid: "bob",
|
|
87
|
+
};
|
|
88
|
+
await clientStorage.createClient(client);
|
|
89
|
+
return retClient;
|
|
90
|
+
}
|
|
91
|
+
function redirect(status, location) {
|
|
92
|
+
throw { status, location };
|
|
93
|
+
}
|
|
94
|
+
;
|
|
95
|
+
function error(status, text) {
|
|
96
|
+
throw { status, text, message: text };
|
|
97
|
+
}
|
|
98
|
+
;
|
|
99
|
+
export async function makeServer(makeSession = true, makeApiKey = false, makeOAuthServer = false, makeOAuthClient = false, options = {}) {
|
|
100
|
+
const keyStorage = new InMemoryKeyStorage();
|
|
101
|
+
const userStorage = new InMemoryUserStorage();
|
|
102
|
+
const clientStorage = new InMemoryOAuthClientStorage();
|
|
103
|
+
const authStorage = new InMemoryOAuthAuthorizationStorage();
|
|
104
|
+
if (makeOAuthServer)
|
|
105
|
+
await createClients(clientStorage);
|
|
106
|
+
const authenticator = new LocalPasswordAuthenticator(userStorage);
|
|
107
|
+
let dummyFactor2Authenticator = new DummyFactor2Authenticator("0000");
|
|
108
|
+
let emailAuthenticator = new EmailAuthenticator();
|
|
109
|
+
await createUsers(userStorage);
|
|
110
|
+
await createClients(clientStorage);
|
|
111
|
+
let session = makeSession ? {
|
|
112
|
+
keyStorage: keyStorage,
|
|
113
|
+
options: {
|
|
114
|
+
allowedFactor2: ["none", "dummyFactor2"],
|
|
115
|
+
}
|
|
116
|
+
} : undefined;
|
|
117
|
+
let apiKey = makeApiKey ? {
|
|
118
|
+
keyStorage: keyStorage
|
|
119
|
+
} : undefined;
|
|
120
|
+
let oAuthAuthServer = makeOAuthServer ? {
|
|
121
|
+
clientStorage,
|
|
122
|
+
keyStorage,
|
|
123
|
+
authenticators: {
|
|
124
|
+
localpassword: authenticator,
|
|
125
|
+
dummyFactor2: dummyFactor2Authenticator,
|
|
126
|
+
email: emailAuthenticator,
|
|
127
|
+
},
|
|
128
|
+
options: {
|
|
129
|
+
userStorage,
|
|
130
|
+
authStorage,
|
|
131
|
+
deviceCodeVerificationUri: "http://localhost:5174/device",
|
|
132
|
+
userCodeThrottle: 0,
|
|
133
|
+
}
|
|
134
|
+
} : undefined;
|
|
135
|
+
let oAuthClient = makeOAuthClient ? {
|
|
136
|
+
authServerBaseUrl: "http://server.com",
|
|
137
|
+
} : undefined;
|
|
138
|
+
const server = new SvelteKitServer({
|
|
139
|
+
session: session,
|
|
140
|
+
apiKey: apiKey,
|
|
141
|
+
oAuthAuthServer: oAuthAuthServer,
|
|
142
|
+
oAuthClient: oAuthClient,
|
|
143
|
+
options: {
|
|
144
|
+
userStorage,
|
|
145
|
+
clientStorage,
|
|
146
|
+
authenticators: {
|
|
147
|
+
localpassword: authenticator,
|
|
148
|
+
dummyFactor2: dummyFactor2Authenticator,
|
|
149
|
+
email: emailAuthenticator,
|
|
150
|
+
},
|
|
151
|
+
secret: "ABCDEFG",
|
|
152
|
+
loginProtectedPageEndpoints: ["/account"],
|
|
153
|
+
factor2ProtectedPageEndpoints: ["/factor2protected"],
|
|
154
|
+
validScopes: ["read", "write"],
|
|
155
|
+
jwtKeyType: "RS256",
|
|
156
|
+
jwtPublicKeyFile: "keys/rsa-public-key.pem",
|
|
157
|
+
jwtPrivateKeyFile: "keys/rsa-private-key.pem",
|
|
158
|
+
tokenResponseType: "sendJson",
|
|
159
|
+
errorResponseType: "sendJson",
|
|
160
|
+
client_id: "ABC",
|
|
161
|
+
client_secret: "DEF",
|
|
162
|
+
redirect_uri: "http://example.com/redirect",
|
|
163
|
+
validFlows: ["all"], // activate all OAuth flows
|
|
164
|
+
bffEndpointName: "/bff",
|
|
165
|
+
bffBaseUrl: "http://server.com",
|
|
166
|
+
bffEndpoints: [
|
|
167
|
+
{ url: "method1", methods: ["GET"], matchSubUrls: false },
|
|
168
|
+
{ url: "method2", methods: ["GET"], matchSubUrls: true },
|
|
169
|
+
],
|
|
170
|
+
tokenEndpoints: [
|
|
171
|
+
"access_token",
|
|
172
|
+
"have_access_token",
|
|
173
|
+
"id_token",
|
|
174
|
+
"have_id_token",
|
|
175
|
+
],
|
|
176
|
+
redirect,
|
|
177
|
+
error,
|
|
178
|
+
...options,
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
const handle = server.hooks;
|
|
182
|
+
const resolver = new MockResolver("Response");
|
|
183
|
+
const apiKeyManager = makeApiKey ? new ApiKeyManager(keyStorage, { secret: "ABCDEFG", }) : undefined;
|
|
184
|
+
return { server, resolver, handle, keyStorage, userStorage, authenticator, apiKeyManager, clientStorage };
|
|
185
|
+
}
|
|
186
|
+
export function getCookies(resp) {
|
|
187
|
+
let cookies = {};
|
|
188
|
+
for (let pair of resp.headers) {
|
|
189
|
+
if (pair[0] == "set-cookie") {
|
|
190
|
+
const parts = pair[1].split("=", 2);
|
|
191
|
+
const semiColon = parts[1].indexOf(";");
|
|
192
|
+
if (semiColon > -1) {
|
|
193
|
+
const value = parts[1].substring(0, semiColon).trim();
|
|
194
|
+
if (value.length > 0)
|
|
195
|
+
cookies[parts[0]] = value;
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
cookies[parts[0]] = parts[1];
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/*
|
|
203
|
+
const cookieHeaders = resp.headers.getSetCookie();
|
|
204
|
+
let cookies : {[key:string]:string} = {};
|
|
205
|
+
for (let cookie of cookieHeaders) {
|
|
206
|
+
const parts = cookie.split("=", 2);
|
|
207
|
+
const semiColon = parts[1].indexOf(";");
|
|
208
|
+
if (semiColon > -1) {
|
|
209
|
+
const value = parts[1].substring(0, semiColon).trim();
|
|
210
|
+
if (value.length > 0) cookies[parts[0]] = value;
|
|
211
|
+
} else {
|
|
212
|
+
cookies[parts[0]] = parts[1];
|
|
213
|
+
}
|
|
214
|
+
}*/
|
|
215
|
+
return cookies;
|
|
216
|
+
}
|
|
217
|
+
export async function getCsrfToken(server, resolver, handle) {
|
|
218
|
+
const getRequest = new Request("http://ex.com/test", { method: "GET" });
|
|
219
|
+
let event = new MockRequestEvent("1", getRequest, { "param1": "value1" });
|
|
220
|
+
const resp = await handle({ event: event, resolve: resolver.mockResolve });
|
|
221
|
+
/*const cookieNames = resp.headers.getSetCookie().map((el) => el.split("=")[0]);
|
|
222
|
+
expect(cookieNames.length).toBe(2);
|
|
223
|
+
expect(["TESTCOOKIE", "CSRFTOKEN"]).toContain(cookieNames[0]);*/
|
|
224
|
+
const cookies = getCookies(resp);
|
|
225
|
+
expect(cookies["CSRFTOKEN"]).toBeDefined();
|
|
226
|
+
let csrfValid = false;
|
|
227
|
+
try {
|
|
228
|
+
server.sessionServer?.sessionManager.validateCsrfCookie(cookies["CSRFTOKEN"]);
|
|
229
|
+
csrfValid = true;
|
|
230
|
+
}
|
|
231
|
+
catch (e) {
|
|
232
|
+
console.log(e);
|
|
233
|
+
}
|
|
234
|
+
expect(csrfValid).toBe(true);
|
|
235
|
+
expect(event.locals.csrfToken).toBeDefined();
|
|
236
|
+
return {
|
|
237
|
+
csrfToken: event.locals.csrfToken,
|
|
238
|
+
csrfCookieValue: cookies["CSRFTOKEN"]
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
export async function login(server, resolver, handle, user = "bob", password = "bobPass123") {
|
|
242
|
+
const { csrfToken, csrfCookieValue } = await getCsrfToken(server, resolver, handle);
|
|
243
|
+
const postRequest = new Request("http://ex.com/test", {
|
|
244
|
+
method: "POST",
|
|
245
|
+
body: "csrfToken=" + csrfToken + "&username=" + user + "&password=" + password,
|
|
246
|
+
headers: {
|
|
247
|
+
"cookie": "CSRFTOKEN=" + csrfCookieValue,
|
|
248
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
let event = new MockRequestEvent("1", postRequest, { "param1": "value1" });
|
|
252
|
+
event.locals.csrfToken = csrfToken;
|
|
253
|
+
const ret = await server.sessionServer?.userEndpoints.login(event);
|
|
254
|
+
expect(ret?.user?.username).toBe(user);
|
|
255
|
+
expect(event.cookies.get("SESSIONID")).toBeDefined();
|
|
256
|
+
return { event, ret };
|
|
257
|
+
}
|
|
258
|
+
;
|
|
259
|
+
export async function loginFactor2(server, resolver, handle, sessionCookieValue, sessionId) {
|
|
260
|
+
const { csrfToken, csrfCookieValue } = await getCsrfToken(server, resolver, handle);
|
|
261
|
+
const postRequest = new Request("http://ex.com/test", {
|
|
262
|
+
method: "POST",
|
|
263
|
+
body: "csrfToken=" + csrfToken + "&otp=0000",
|
|
264
|
+
headers: [
|
|
265
|
+
["set-cookie", "CSRFTOKEN=" + csrfCookieValue],
|
|
266
|
+
["set-cookie", "SESSIONID=" + sessionCookieValue],
|
|
267
|
+
["content-type", "application/x-www-form-urlencoded"],
|
|
268
|
+
]
|
|
269
|
+
});
|
|
270
|
+
let event = new MockRequestEvent("1", postRequest, { "param1": "value1" });
|
|
271
|
+
event.locals.csrfToken = csrfToken;
|
|
272
|
+
event.locals.sessionId = sessionId;
|
|
273
|
+
const ret = await server.sessionServer?.userEndpoints.loginFactor2(event);
|
|
274
|
+
return { event, ret };
|
|
275
|
+
}
|
|
276
|
+
;
|
|
277
|
+
export async function getAuthServer({ aud, persistAccessToken, emptyScopeIsValid, secretRequired, rollingRefreshToken, } = {}) {
|
|
278
|
+
const clientStorage = new InMemoryOAuthClientStorage();
|
|
279
|
+
const client = await createClients(clientStorage, secretRequired == undefined || secretRequired == true);
|
|
280
|
+
const privateKey = fs.readFileSync("keys/rsa-private-key.pem", 'utf8');
|
|
281
|
+
const userStorage = new InMemoryUserStorage();
|
|
282
|
+
await createUsers(userStorage);
|
|
283
|
+
const lpAuthenticator = new LocalPasswordAuthenticator(userStorage);
|
|
284
|
+
const dummyFactor2 = new DummyFactor2Authenticator("0000");
|
|
285
|
+
const authenticators = {
|
|
286
|
+
"localpassword": lpAuthenticator,
|
|
287
|
+
"dummyFactor2": dummyFactor2,
|
|
288
|
+
};
|
|
289
|
+
let options = {
|
|
290
|
+
jwtKeyType: "RS256",
|
|
291
|
+
jwtPrivateKey: privateKey,
|
|
292
|
+
jwtPublicKeyFile: "keys/rsa-public-key.pem",
|
|
293
|
+
validateScopes: true,
|
|
294
|
+
validScopes: ["read", "write"],
|
|
295
|
+
issueRefreshToken: true,
|
|
296
|
+
emptyScopeIsValid: emptyScopeIsValid,
|
|
297
|
+
validFlows: ["all"],
|
|
298
|
+
userStorage,
|
|
299
|
+
authStorage: new InMemoryOAuthAuthorizationStorage(),
|
|
300
|
+
};
|
|
301
|
+
if (aud)
|
|
302
|
+
options.audience = aud;
|
|
303
|
+
if (persistAccessToken) {
|
|
304
|
+
options.persistAccessToken = true;
|
|
305
|
+
}
|
|
306
|
+
if (rollingRefreshToken != undefined)
|
|
307
|
+
options.rollingRefreshToken = rollingRefreshToken;
|
|
308
|
+
const keyStorage = new InMemoryKeyStorage();
|
|
309
|
+
const authServer = new OAuthAuthorizationServer(clientStorage, keyStorage, authenticators, options);
|
|
310
|
+
return { client, clientStorage, authServer, keyStorage, userStorage };
|
|
311
|
+
}
|
|
312
|
+
export async function getAuthorizationCode({ challenge, aud, persistAccessToken, rollingRefreshToken, } = {}) {
|
|
313
|
+
const secretRequired = challenge == undefined;
|
|
314
|
+
const { client, clientStorage, authServer, keyStorage, userStorage } = await getAuthServer({ challenge, aud, persistAccessToken, secretRequired, rollingRefreshToken });
|
|
315
|
+
const { user } = await userStorage.getUserByUsername("bob");
|
|
316
|
+
const inputState = "ABCXYZ";
|
|
317
|
+
let codeChallenge;
|
|
318
|
+
const codeVerifier = "ABC123";
|
|
319
|
+
if (challenge)
|
|
320
|
+
codeChallenge = Crypto.hash(codeVerifier);
|
|
321
|
+
const { code, error, error_description } = await authServer.authorizeGetEndpoint({
|
|
322
|
+
responseType: "code",
|
|
323
|
+
client_id: client.client_id,
|
|
324
|
+
redirect_uri: client.redirect_uri[0],
|
|
325
|
+
scope: "read write",
|
|
326
|
+
state: inputState,
|
|
327
|
+
codeChallenge: codeChallenge,
|
|
328
|
+
user
|
|
329
|
+
});
|
|
330
|
+
expect(error).toBeUndefined();
|
|
331
|
+
expect(error_description).toBeUndefined();
|
|
332
|
+
return { code, client, clientStorage, authServer, keyStorage };
|
|
333
|
+
}
|
|
334
|
+
export async function getAccessToken() {
|
|
335
|
+
const { authServer, client, code, clientStorage } = await getAuthorizationCode();
|
|
336
|
+
const { access_token, error, error_description, refresh_token, expires_in } = await authServer.tokenEndpoint({
|
|
337
|
+
grantType: "authorization_code",
|
|
338
|
+
client_id: client.client_id,
|
|
339
|
+
code: code,
|
|
340
|
+
client_secret: "DEF"
|
|
341
|
+
});
|
|
342
|
+
return { authServer, client, code, clientStorage, access_token, error, error_description, refresh_token, expires_in };
|
|
343
|
+
}
|
|
344
|
+
;
|
package/dist/utils.d.ts
CHANGED