@sourceregistry/sveltekit-oidc 1.6.6 → 1.6.8

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.
@@ -6,7 +6,8 @@
6
6
  </script>
7
7
 
8
8
  <script lang="ts" generics="TClaims extends OIDCUserClaims = OIDCUserClaims">
9
- import {goto, invalidateAll} from '$app/navigation';
9
+ import {invalidateAll} from '$app/navigation';
10
+ import {tick} from 'svelte';
10
11
  import type {Snippet} from 'svelte';
11
12
 
12
13
  import {setOIDCContext} from './context.js';
@@ -21,6 +22,7 @@
21
22
  checkSessionIntervalMs = 5000,
22
23
  monitorSession = true,
23
24
  revalidateIntervalMs = 30000,
25
+ renewalLeadTimeMs = 5000,
24
26
  redirectOnExpired = 'login',
25
27
  redirectOnRevoked = 'login',
26
28
  redirectIfUnauthenticated = false,
@@ -33,6 +35,7 @@
33
35
  checkSessionIntervalMs?: number;
34
36
  monitorSession?: boolean;
35
37
  revalidateIntervalMs?: number;
38
+ renewalLeadTimeMs?: number;
36
39
  redirectOnExpired?: RedirectMode;
37
40
  redirectOnRevoked?: RedirectMode;
38
41
  redirectIfUnauthenticated?: boolean;
@@ -101,7 +104,9 @@
101
104
  }
102
105
 
103
106
  function login(returnTo?: string) {
104
- void goto(buildLoginUrl(returnTo), {invalidateAll: false});
107
+ // loginPath is a plain +server.ts redirect endpoint, not a routable
108
+ // page — use a full browser navigation, not SvelteKit's goto().
109
+ window.location.href = buildLoginUrl(returnTo);
105
110
  }
106
111
 
107
112
  async function revalidate() {
@@ -130,17 +135,39 @@
130
135
  status = isAuthenticated ? 'authenticated' : status === 'authenticated' ? 'unauthenticated' : status;
131
136
  });
132
137
 
138
+ let lastExpiresAt: number | undefined;
139
+ let renewalAttemptedForExpiresAt: number | undefined;
140
+
133
141
  $effect(() => {
134
142
  if (!session?.isAuthenticated || !session.expiresAt) {
135
143
  return;
136
144
  }
137
145
 
138
- const timeoutMs = Math.max(0, session.expiresAt * 1000 - Date.now());
146
+ // If a prior revalidate already came back with the same (still-past)
147
+ // expiresAt, the server couldn't refresh the token (e.g. no refresh
148
+ // token available) — retrying immediately forever would spin-loop.
149
+ // Treat that as a hard expiry instead of rescheduling.
150
+ if (
151
+ lastExpiresAt !== undefined &&
152
+ session.expiresAt <= lastExpiresAt &&
153
+ session.expiresAt * 1000 <= Date.now()
154
+ ) {
155
+ status = 'expired';
156
+ void handleRedirect(redirectOnExpired);
157
+ return;
158
+ }
159
+ lastExpiresAt = session.expiresAt;
160
+
161
+ const expiresAt = session.expiresAt;
162
+ const leadTimeMs = renewalAttemptedForExpiresAt === expiresAt ? 0 : renewalLeadTimeMs;
163
+ const timeoutMs = Math.max(0, expiresAt * 1000 - Date.now() - leadTimeMs);
139
164
  const timer = window.setTimeout(async () => {
140
165
  // Silent revalidate first — server's maybeRefreshSession will refresh
141
166
  // the token if a valid refresh_token exists. Only redirect if the
142
167
  // session comes back unauthenticated after that.
168
+ renewalAttemptedForExpiresAt = expiresAt;
143
169
  await revalidate();
170
+ await tick();
144
171
  if (!session?.isAuthenticated) {
145
172
  status = 'expired';
146
173
  void handleRedirect(redirectOnExpired);
@@ -9,6 +9,7 @@ declare function $$render<TClaims extends OIDCUserClaims = OIDCUserClaims>(): {
9
9
  checkSessionIntervalMs?: number;
10
10
  monitorSession?: boolean;
11
11
  revalidateIntervalMs?: number;
12
+ renewalLeadTimeMs?: number;
12
13
  redirectOnExpired?: "none" | "login" | "logout" | "reload";
13
14
  redirectOnRevoked?: "none" | "login" | "logout" | "reload";
14
15
  redirectIfUnauthenticated?: boolean;
@@ -479,7 +479,7 @@ export function createOIDC(options) {
479
479
  };
480
480
  async function hook(event) {
481
481
  const session = await getSession(event);
482
- return {
482
+ const locals = {
483
483
  oidc: {
484
484
  isAuthenticated: Boolean(session),
485
485
  session,
@@ -494,6 +494,8 @@ export function createOIDC(options) {
494
494
  clearSession: async () => clearPersistedSession(event.cookies)
495
495
  }
496
496
  };
497
+ event.locals.oidc = locals.oidc;
498
+ return locals;
497
499
  }
498
500
  function loginHandler(defaults = {}) {
499
501
  return async (event) => {
@@ -506,9 +508,8 @@ export function createOIDC(options) {
506
508
  try {
507
509
  const result = await handleCallback(event);
508
510
  const response = await handlerOptions.onsuccess?.(event, result);
509
- if (response) {
511
+ if (response)
510
512
  return response;
511
- }
512
513
  throw redirect(302, internalRedirectPath(event, handlerOptions.redirectTo ?? result.returnTo, '/'));
513
514
  }
514
515
  catch (err) {
@@ -5,8 +5,9 @@ import { base64UrlEncode } from './utils.js';
5
5
  export async function fetchJson(url, init) {
6
6
  const response = await fetch(url, init);
7
7
  if (!response.ok) {
8
+ const body = await response.text().catch(() => '');
8
9
  throw error(response.status, {
9
- message: `OIDC request failed for '${url}' with status ${response.status}`
10
+ message: `OIDC request failed for '${url}' with status ${response.status}: ${body}`
10
11
  });
11
12
  }
12
13
  return (await response.json());
@@ -244,6 +244,7 @@ export type OIDCInstance<TClaims extends OIDCUserClaims = OIDCUserClaims, TSessi
244
244
  handle: Handle;
245
245
  hook: (event: {
246
246
  cookies: RequestEvent['cookies'];
247
+ locals: App.Locals;
247
248
  }) => Promise<{
248
249
  oidc: OIDCHandleLocals<TClaims, TSession>;
249
250
  }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sourceregistry/sveltekit-oidc",
3
- "version": "1.6.6",
3
+ "version": "1.6.8",
4
4
  "description": "OIDC authentication helpers for SvelteKit applications",
5
5
  "license": "Apache-2.0",
6
6
  "scripts": {