@crossauth/sveltekit 1.1.0 → 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.
Files changed (46) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +16 -6181
  3. package/dist/sveltekitadminclientendpoints.d.ts +13 -12
  4. package/dist/sveltekitadminclientendpoints.js +187 -0
  5. package/dist/sveltekitadminendpoints.d.ts +5 -4
  6. package/dist/sveltekitadminendpoints.js +766 -0
  7. package/dist/sveltekitapikey.d.ts +4 -4
  8. package/dist/sveltekitapikey.js +81 -0
  9. package/dist/sveltekitoauthclient.d.ts +6 -5
  10. package/dist/sveltekitoauthclient.js +2309 -0
  11. package/dist/sveltekitoauthserver.d.ts +4 -4
  12. package/dist/sveltekitoauthserver.js +1350 -0
  13. package/dist/sveltekitresserver.d.ts +6 -5
  14. package/dist/sveltekitresserver.js +286 -0
  15. package/dist/sveltekitserver.d.ts +11 -10
  16. package/dist/sveltekitserver.js +393 -0
  17. package/dist/sveltekitsession.d.ts +5 -5
  18. package/dist/sveltekitsession.js +1112 -0
  19. package/dist/sveltekitsessionadapter.d.ts +2 -3
  20. package/dist/sveltekitsessionadapter.js +2 -0
  21. package/dist/sveltekitsharedclientendpoints.d.ts +7 -6
  22. package/dist/sveltekitsharedclientendpoints.js +630 -0
  23. package/dist/sveltekituserclientendpoints.d.ts +13 -12
  24. package/dist/sveltekituserclientendpoints.js +270 -0
  25. package/dist/sveltekituserendpoints.d.ts +6 -5
  26. package/dist/sveltekituserendpoints.js +1813 -0
  27. package/dist/tests/sveltekitadminclientendpoints.test.js +330 -0
  28. package/dist/tests/sveltekitadminendpoints.test.js +242 -0
  29. package/dist/tests/sveltekitapikeyserver.test.js +44 -0
  30. package/dist/tests/sveltekitoauthclient.test.d.ts +5 -5
  31. package/dist/tests/sveltekitoauthclient.test.js +1016 -0
  32. package/dist/tests/sveltekitoauthresserver.test.d.ts +4 -4
  33. package/dist/tests/sveltekitoauthresserver.test.js +185 -0
  34. package/dist/tests/sveltekitoauthserver.test.js +673 -0
  35. package/dist/tests/sveltekituserclientendpoints.test.js +244 -0
  36. package/dist/tests/sveltekituserendpoints.test.js +152 -0
  37. package/dist/tests/sveltemock.test.js +36 -0
  38. package/dist/tests/sveltemocks.d.ts +2 -3
  39. package/dist/tests/sveltemocks.js +114 -0
  40. package/dist/tests/sveltesessionhooks.test.js +224 -0
  41. package/dist/tests/testshared.d.ts +8 -8
  42. package/dist/tests/testshared.js +344 -0
  43. package/dist/utils.d.ts +1 -2
  44. package/dist/utils.js +123 -0
  45. package/package.json +6 -4
  46. package/dist/index.cjs +0 -1
@@ -0,0 +1,393 @@
1
+ // Copyright (c) 2026 Matthew Baker. All rights reserved. Licenced under the Apache Licence 2.0. See LICENSE file
2
+ import { SvelteKitSessionServer } from './sveltekitsession';
3
+ import { SvelteKitApiKeyServer } from './sveltekitapikey';
4
+ import { SvelteKitAuthorizationServer } from './sveltekitoauthserver';
5
+ import { UserStorage, KeyStorage, Authenticator, setParameter, ParamType, OAuthClientStorage } from '@crossauth/backend';
6
+ import { CrossauthError, CrossauthLogger, ErrorCode, j } from '@crossauth/common';
7
+ import {} from '@sveltejs/kit';
8
+ import {} from './tests/sveltemocks';
9
+ import { SvelteKitOAuthClient } from './sveltekitoauthclient';
10
+ import { SvelteKitOAuthResourceServer } from './sveltekitresserver';
11
+ import { OAuthTokenConsumer } from '@crossauth/backend';
12
+ import { SvelteKitSessionAdapter } from './sveltekitsessionadapter';
13
+ /**
14
+ * The function to determine if a user has admin rights can be set
15
+ * externally. This is the default function if none other is set.
16
+ * It returns true iff the `admin` field in the passed user is set to true.
17
+ *
18
+ * @param user the user to test
19
+ * @returns true or false
20
+ */
21
+ function defaultIsAdminFn(user) {
22
+ return user.admin == true;
23
+ }
24
+ /**
25
+ * This is the main class for adding Crossauth to Svelekit applications.
26
+ *
27
+ * To use it, create a file in your `src/lib` directory, eg
28
+ * `src/lib/crossauthsessiion.ts`, something like this:
29
+ *
30
+ * ```
31
+ * export const prisma = new PrismaClient();
32
+ * const userStorage = new PrismaUserStorage({prismaClient : prisma, userEditableFields: ["email"]});
33
+ * const keyStorage = new PrismaKeyStorage({prismaClient : prisma});
34
+ * const passwordAuthenticator = new LocalPasswordAuthenticator(userStorage);
35
+ * export const crossauth = new SvelteKitServer({
36
+ * session: {
37
+ * keyStorage: keyStorage,
38
+ * }}, {
39
+ * userStorage: userStorage,
40
+ * authenticators: {
41
+ * localpassword: passwordAuthenticator,
42
+ * },
43
+ * loginProtectedPageEndpoints: ["/account"],
44
+ * redirect,
45
+ * error
46
+ * });
47
+ * ```
48
+ *
49
+ * Note that we pass Sveltekit's `action` and `error` methods because, as
50
+ * a module compiled without your Sveltekit application, this class has
51
+ * no access to them otherwise, and they are use internally for things like
52
+ * redirecting to your login page.
53
+ *
54
+ * **Component Servers**
55
+ *
56
+ * The above example creates a ccookie-based session server. This class
57
+ * has several optional component servers which can be instantiated
58
+ * individually or together. They are:
59
+ *
60
+ * - `sessionServer` Session cookie management server. Uses sesion ID
61
+ * and CSRF cookies. See {@link SvelteKitSessionServer}.
62
+ * - `sessionAdapter` If you are using only the oAuthClient and don't want
63
+ * to use Crossauth's session server, you can implement
64
+ * a minimal {@link SvelteKitSessionAdapter} instead.
65
+ * - `oAuthAuthServer` OAuth authorization server. See
66
+ * {@link SvelteKitAuthorizationServer}
67
+ * - `oAuthClient` OAuth client. See {@link SvelteKitOAuthClient}.
68
+ * - `oAuthClients` Array of OAuth clients if you want more than one. See {@link SvelteKitOAuthClient}.
69
+ * - `oAuthResServer` OAuth resource server. See
70
+ * {@link SvelteKitOAuthResourceServer}.
71
+ *
72
+ * Use either `oAuthClient` or `oAuthClients` but not both.
73
+ *
74
+ * There is also an API key server which is not available as a variable as
75
+ * it has no functions other than the hook it registers.
76
+ * See {@link SvelteKitApiKeyServer}.
77
+ *
78
+ * **Hooks**
79
+ *
80
+ * This class provides hooks which you can add to by putting the following
81
+ * in your `hooks.server.ts`:
82
+ *
83
+ * ```
84
+ * import { type Handle } from '@sveltejs/kit';
85
+ * import { crossauth } from './server/crossauthsession';
86
+ * import { CrossauthLogger } from '@crossauth/common';
87
+ * export const handle: Handle = crossauth.hooks;
88
+ * ```
89
+ *
90
+ * **Locals**
91
+ *
92
+ * This will set the following in `event.locals`:
93
+ *
94
+ * - `user`: the logged in {@link @crossauth/common!User} or undefined,
95
+ * - `csrfToken` a CSRF token if the request is a `GET`, `HEAD` or `OPTIONS`,
96
+ * - `authType` authentication type, currently only `cookie`,
97
+ * - `apiKey` the valid API key if one was used,
98
+ * - `oAuthAuthServer` OAuth authorization server. See
99
+ * {@link SvelteKitAuthorizationServer}
100
+ * - `accessTokenPayload` payload for the OAuth access token (not currently supported),
101
+ * - `authError` string error during authentication process (not currently used)
102
+ * - `authErrorDescription` error during authentication (not currently used),
103
+ * - `sessionId` session ID of logged in user, session ID for anonymous user, or undefined,
104
+ * - `scope` oAuth scope, not currently used,
105
+ *
106
+ * **Authenticators**
107
+ *
108
+ * One and two factor authentication is supported. Authentication is provided
109
+ * by classes implementing {@link Authenticator}. They are passed as an
110
+ * object to this class, keyed on the name that appears in the user record
111
+ * as `factor1` or `factor2`.
112
+ *
113
+ * For example, if you have passwords in your user database, you can use
114
+ * {@link @crossauth/backend!LocalPasswordAuthenticator}. If this method of authentication
115
+ * is called `password` in the `factor1` field of the user record,
116
+ * pass it in the `authenticators` parameter in the constructor with a key
117
+ * of `password`.
118
+ *
119
+ * **Use in Pages**
120
+ *
121
+ * For instructions about how to use this class in your endpoints, see
122
+ * {@link SvelteKitUserEndpoints} and {@link SvelteKitAdminEndpoints}
123
+ * for cookie-based session management.
124
+ */
125
+ export class SvelteKitServer {
126
+ /** The User storage that was passed during construction */
127
+ userStorage;
128
+ /** The session server if one was requested during construction */
129
+ sessionServer;
130
+ /** See class documentation. If you pass `sessionServer` here instead,
131
+ * `sessionAdapter` will also be set to it
132
+ */
133
+ sessionAdapter;
134
+ /** The api key server if one was requested during construction */
135
+ apiKeyServer;
136
+ /** The OAuth authorization server if one was requested */
137
+ oAuthAuthServer;
138
+ /** For adding in your `hooks.server.ts. See class documentation
139
+ * for details
140
+ */
141
+ hooks;
142
+ loginUrl = "/login";
143
+ /**
144
+ * User-defined function for determining whether a user is an admin.
145
+ *
146
+ * The default is to look at the `admin` member of the
147
+ * {@link @crossauth/common!User} object.
148
+ */
149
+ static isAdminFn = defaultIsAdminFn;
150
+ /**
151
+ * OAuth client instance
152
+ */
153
+ oAuthClient;
154
+ /**
155
+ * Array of OAuth client instances as an alternative to `oAuthClient`
156
+ */
157
+ oAuthClients;
158
+ /** OAuth resource server instance */
159
+ oAuthResServer;
160
+ audience = "";
161
+ /**
162
+ * Constructor.
163
+ *
164
+ * @param config an object with configuration:
165
+ * - `session` if you want a session (session cookie-based
166
+ * authentication), include this. See the class documentation for
167
+ * details. Note that the options in the third parameter of this
168
+ * constructor are concatinated with the options defined in
169
+ * `session.options`, so that you can have global as well as
170
+ * session server-specific configuration.
171
+ * - `apiKey` if passed, instantiate the session server (see class
172
+ * documentation). The value is an object with a `keyStorage` field
173
+ * which must be present and should be the {@link KeyStorage} instance
174
+ * where API keys are stored. A field called `options` whose
175
+ * value is an {@link SvelteKitApiKeyServerOptions} may also be
176
+ * provided.
177
+ * - `oAuthAuthServer` if passed, instantiate the session server (see class
178
+ * documentation). The value is an object with a `keyStorage` field
179
+ * which must be present and should be the {@link KeyStorage} instance
180
+ * where authorization codes are stored. This may be the same as
181
+ * the table storing session IDs or may be different. A field
182
+ * called `clientStorage` with a value of type {@link OAuthClientStorage}
183
+ * must be provided and is where OAuth client details are stored.
184
+ * A field called `options` whose
185
+ * value is an {@link SvelteKitAuthorizationServerOptions} may also be
186
+ * provided.
187
+ * - `oAuthClient` if present, an OAuth client will be created.
188
+ * There must be a field called `authServerBaseUrl` and is the
189
+ * base URL for the authorization server. When validating access
190
+ * tokens, the `iss` claim must match this.
191
+ * - `oAuthClients` use this instead of `oAuthClient` if you want more
192
+ * than one OAuth client.
193
+ * - `oAuthResServer` OAuth resource server. See
194
+ * {@link SvelteKitOAuthResourceServer}.
195
+ * - `options` Configuration that applies to the whole application,
196
+ * not just the session server.
197
+ */
198
+ constructor({ session, sessionAdapter, apiKey, oAuthAuthServer, oAuthClient, oAuthClients, oAuthResServer, options, }) {
199
+ if (!options)
200
+ options = {};
201
+ setParameter("loginUrl", ParamType.String, this, options, "LOGIN_URL", false);
202
+ if (options.isAdminFn)
203
+ SvelteKitServer.isAdminFn = options.isAdminFn;
204
+ let authenticators = {};
205
+ if (options.authenticators)
206
+ authenticators = options.authenticators;
207
+ this.userStorage = options.userStorage;
208
+ if (session) {
209
+ if (!authenticators) {
210
+ throw new CrossauthError(ErrorCode.Configuration, "If using session management, must supply authenticators");
211
+ }
212
+ this.sessionServer = new SvelteKitSessionServer(session.keyStorage, authenticators, { ...session.options, ...options });
213
+ this.sessionAdapter = this.sessionServer;
214
+ }
215
+ else if (sessionAdapter) {
216
+ this.sessionAdapter = sessionAdapter;
217
+ }
218
+ if (apiKey) {
219
+ if (!this.userStorage)
220
+ throw new CrossauthError(ErrorCode.Configuration, "Must define a user storage if using API keys");
221
+ this.apiKeyServer = new SvelteKitApiKeyServer(this.userStorage, apiKey.keyStorage, { ...options, ...apiKey.options });
222
+ }
223
+ if (oAuthAuthServer) {
224
+ let extraOptions = {};
225
+ if (this.loginUrl)
226
+ extraOptions.loginUrl = this.loginUrl;
227
+ this.oAuthAuthServer = new SvelteKitAuthorizationServer(this, oAuthAuthServer.clientStorage, oAuthAuthServer.keyStorage, authenticators, { ...extraOptions, ...options, ...oAuthAuthServer.options });
228
+ }
229
+ if (oAuthClient && oAuthClients) {
230
+ throw new CrossauthError(ErrorCode.Configuration, "Cannot specify both oAuthClient and oAuthClients");
231
+ }
232
+ if (oAuthClient) {
233
+ this.oAuthClient = new SvelteKitOAuthClient(this, oAuthClient.authServerBaseUrl, { ...options, ...oAuthClient.options });
234
+ }
235
+ if (oAuthClients) {
236
+ this.oAuthClients = [];
237
+ for (let client of oAuthClients) {
238
+ this.oAuthClients.push(new SvelteKitOAuthClient(this, client.authServerBaseUrl, { ...options, ...client.options }));
239
+ }
240
+ }
241
+ if (oAuthResServer) {
242
+ setParameter("audience", ParamType.String, this, options, "OAUTH_AUDIENCE", true);
243
+ this.oAuthResServer = new SvelteKitOAuthResourceServer([new OAuthTokenConsumer(this.audience, options)], { sessionAdapter: this.sessionAdapter, ...oAuthResServer.options, ...options });
244
+ }
245
+ this.hooks = async ({ event, resolve }) => {
246
+ const newEvent = await this.unresolvedHooks(event);
247
+ if (newEvent instanceof Response)
248
+ return newEvent;
249
+ return await resolve(newEvent);
250
+ };
251
+ }
252
+ async unresolvedHooks(event) {
253
+ // reset all locals
254
+ event.locals.user = undefined;
255
+ event.locals.sessionId = undefined;
256
+ event.locals.csrfToken = undefined;
257
+ event.locals.authType = undefined;
258
+ event.locals.scope = undefined;
259
+ let otherLoginsTried = false;
260
+ if (this.sessionServer) {
261
+ // session hook
262
+ let resp = await this.sessionServer.sessionHook({ event });
263
+ if (resp.status == 302) {
264
+ let loc = undefined;
265
+ for (let h of resp.headers) {
266
+ if (h.name == "location")
267
+ loc = h.value;
268
+ }
269
+ if (loc)
270
+ await this.sessionServer.redirect(302, loc);
271
+ }
272
+ // two FA hook
273
+ const ret = this.userStorage ? await this.sessionServer.twoFAHook({ event }) : undefined;
274
+ if (!(ret && ret.twofa) && !event.locals.user) {
275
+ // try other means of logging in before redirecting to login page
276
+ // API server hook
277
+ if (this.apiKeyServer) {
278
+ await this.apiKeyServer.hook({ event });
279
+ }
280
+ // OAuth client hook
281
+ if (this.oAuthClient) {
282
+ await this.oAuthClient.hook({ event });
283
+ }
284
+ // OAuth res server hook
285
+ if (this.oAuthResServer?.hook) {
286
+ const resp = await this.oAuthResServer.hook({ event });
287
+ if (resp)
288
+ return resp;
289
+ }
290
+ otherLoginsTried = true;
291
+ if (!event.locals.user) {
292
+ if (this.sessionServer.isLoginPageProtected(event)) {
293
+ CrossauthLogger.logger.debug(j({ msg: "Page is login protected and we don't have credentials" }));
294
+ if (this.loginUrl) {
295
+ /*let redirect_uri = event.url.pathname;
296
+ if (event.url.searchParams) {
297
+ redirect_uri += "%3F";
298
+ event.url.searchParams.forEach((value, key) => {
299
+ redirect_uri += encodeURIComponent(key) + "%3D" + encodeURIComponent(value)
300
+ });
301
+ }*/
302
+ let redirect_uri = encodeURIComponent(event.request.url);
303
+ return new Response(null, { status: 302, headers: { location: this.loginUrl + "?next=" + redirect_uri } });
304
+ }
305
+ return this.sessionServer.error(401, "Unauthorized");
306
+ }
307
+ if (this.sessionServer.isLoginApiProtected(event))
308
+ return this.sessionServer.error(401, "Unauthorized");
309
+ }
310
+ }
311
+ if (!(ret && ret.twofa) && this.sessionServer.isAdminPageEndpoint(event) &&
312
+ (!event.locals.user || !SvelteKitServer.isAdminFn(event.locals.user))) {
313
+ if (this.sessionServer.unauthorizedUrl) {
314
+ return new Response(null, { status: 302, headers: { location: this.sessionServer.unauthorizedUrl } });
315
+ }
316
+ return this.sessionServer.error(401, "Unauthorized");
317
+ }
318
+ if (!(ret && ret.twofa) && this.sessionServer.isAdminApiEndpoint(event) &&
319
+ (!event.locals.user || !SvelteKitServer.isAdminFn(event.locals.user))) {
320
+ return this.sessionServer.error(401, "Unauthorized");
321
+ }
322
+ if (ret?.response)
323
+ return ret.response;
324
+ }
325
+ if (!otherLoginsTried) {
326
+ // API server hook
327
+ if (this.apiKeyServer) {
328
+ await this.apiKeyServer.hook({ event });
329
+ }
330
+ // OAuth client hook
331
+ if (this.oAuthClient) {
332
+ await this.oAuthClient.hook({ event });
333
+ }
334
+ // OAuth res server hook
335
+ if (this.oAuthResServer?.hook) {
336
+ const resp = await this.oAuthResServer.hook({ event });
337
+ if (resp)
338
+ return resp;
339
+ }
340
+ }
341
+ return event;
342
+ }
343
+ /**
344
+ * See class documentation for {@link SvelteKitUserEndpoints}.
345
+ *
346
+ * This is an empty `load` which serves no purpose other than to stop
347
+ * Typescript complaining that `load` may be undefined.
348
+ * @param _event Sveltekit event object
349
+ * @returns an empty object
350
+ */
351
+ dummyLoad = async (_event) => { return {}; };
352
+ emptyLoad = async (_event) => { return {}; };
353
+ /**
354
+ * See class documentation for {@link SvelteKitUserEndpoints}.
355
+ *
356
+ * This is an empty `load` which serves no purpose other than to stop
357
+ * Typescript complaining that `actions` may be undefined.
358
+ * @returns an empty object
359
+ */
360
+ dummyActions = {};
361
+ /**
362
+ * See class documentation for {@link SvelteKitUserEndpoints}.
363
+ *
364
+ * This is an empty `bff action` which serves no purpose other than to stop
365
+ * Typescript complaining that the action may be undefined.
366
+ * @param _event Sveltekit event object
367
+ * @returns an empty object
368
+ */
369
+ dummyBff = async (_event) => { return { status: 500, body: { error: "Unimplemented" } }; };
370
+ /**
371
+ * It is not possible to get any meaninfgul info about an exception class
372
+ * with `typeof` or `instanceof`. This method heuristically determines
373
+ * if an exception is a Sveltekit redirect. It is used internally
374
+ * @param e an exception
375
+ * @returns true or false
376
+ */
377
+ static isSvelteKitRedirect(e) {
378
+ return (typeof e == "object" && e != null && "status" in e && "location" in e);
379
+ }
380
+ /**
381
+ * It is not possible to get any meaninfgul info about an exception class
382
+ * with `typeof` or `instanceof`. This method heuristically determines
383
+ * if an exception is a Sveltekit error. It is used internally
384
+ * @param e an exception
385
+ * @returns true or false
386
+ */
387
+ static isSvelteKitError(e, status) {
388
+ if (status) {
389
+ return (typeof e == "object" && e != null && "status" in e && "text" in e && "message" in e && e.status == status);
390
+ }
391
+ return (typeof e == "object" && e != null && "status" in e && "text" in e && "message" in e);
392
+ }
393
+ }
@@ -1,13 +1,13 @@
1
- import { KeyStorage, UserStorage, OAuthClientStorage, SessionManager, Authenticator, Cookie, SessionManagerOptions } from '@crossauth/backend';
2
- import { Key, User, UserInputFields, OAuthClient } from '@crossauth/common';
3
- import { RequestEvent } from '@sveltejs/kit';
1
+ import { KeyStorage, UserStorage, OAuthClientStorage, SessionManager, Authenticator } from '@crossauth/backend';
2
+ import type { Cookie, SessionManagerOptions } from '@crossauth/backend';
3
+ import type { Key, User, UserInputFields, OAuthClient } from '@crossauth/common';
4
+ import type { RequestEvent } from '@sveltejs/kit';
4
5
  import { SvelteKitUserEndpoints } from './sveltekituserendpoints';
5
6
  import { SvelteKitAdminEndpoints } from './sveltekitadminendpoints';
6
7
  import { SvelteKitUserClientEndpoints } from './sveltekituserclientendpoints';
7
8
  import { SvelteKitAdminClientEndpoints } from './sveltekitadminclientendpoints';
8
9
  import { SvelteKitSessionAdapter } from './sveltekitsessionadapter';
9
- import { MaybePromise } from './tests/sveltemocks';
10
-
10
+ import { type MaybePromise } from './tests/sveltemocks';
11
11
  export declare const CSRFHEADER = "X-CROSSAUTH-CSRF";
12
12
  export type Header = {
13
13
  name: string;