@snokam/auth 5.5.3 → 5.5.5

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/CHANGELOG.md CHANGED
@@ -3,6 +3,16 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [5.5.5](https://github.com/snokam/components/compare/@snokam/auth@5.5.4...@snokam/auth@5.5.5) (2026-10-28)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **platform-web:** platform sections run as yourself, also while viewing as a persona ([#3046](https://github.com/snokam/components/issues/3046)) ([3ef73be](https://github.com/snokam/components/commit/3ef73be782f088cb4fe2174ab754d172aecb1bb1))
11
+
12
+ ## [5.5.4](https://github.com/snokam/components/compare/@snokam/auth@5.5.3...@snokam/auth@5.5.4) (2026-10-28)
13
+
14
+ **Note:** Version bump only for package @snokam/auth
15
+
6
16
  ## [5.5.3](https://github.com/snokam/components/compare/@snokam/auth@5.5.2...@snokam/auth@5.5.3) (2026-10-26)
7
17
 
8
18
  **Note:** Version bump only for package @snokam/auth
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@snokam/auth",
3
3
  "description": "Snøkam auth components",
4
- "version": "5.5.3",
4
+ "version": "5.5.5",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
7
7
  "files": [
@@ -27,10 +27,10 @@
27
27
  "next-auth": "4.24.15",
28
28
  "path-to-regexp": "^8.4.2",
29
29
  "@snokam/identity": "1.1.0",
30
- "@snokam/platform-client": "5.3.1",
30
+ "@snokam/platform-client": "5.3.2",
31
31
  "@snokam/react-query": "1.1.0",
32
- "@snokam/sanity-page-content": "6.8.0",
33
- "@snokam/tenant": "4.4.0",
32
+ "@snokam/sanity-page-content": "6.8.1",
33
+ "@snokam/tenant": "4.4.1",
34
34
  "@snokam/ui": "6.4.1"
35
35
  },
36
36
  "peerDependencies": {
@@ -21,7 +21,8 @@ export {
21
21
  PLATFORM_ADMIN,
22
22
  PLATFORM_OPERATOR,
23
23
  } from "../roles/context";
24
- export type { RolesState } from "../roles/context";
24
+ export type { RolesState, PlatformRolesOptions } from "../roles/context";
25
+ export { OwnIdentity } from "../identity/own";
25
26
  export type { ScopesState } from "../scopes";
26
27
  export {
27
28
  scopeGranted,
package/src/hooks.ts CHANGED
@@ -2,6 +2,7 @@ import { signIn, signOut } from "next-auth/react";
2
2
  import { useMemo } from "react";
3
3
  import { IMP_ACTIVE_COOKIE } from "./impersonation/cookies";
4
4
  import { useImpersonation } from "./impersonation/context";
5
+ import { useOwnIdentity } from "./identity/own";
5
6
  import { useSession } from "./session/useSession";
6
7
 
7
8
  class ConsentRequiredError extends Error {}
@@ -14,8 +15,16 @@ interface CachedToken {
14
15
  impKey: string;
15
16
  }
16
17
 
17
- let cachedToken: CachedToken | null = null;
18
- let inFlight: { impKey: string; promise: Promise<string> } | null = null;
18
+ type TokenKind = "session" | "own";
19
+
20
+ const cachedTokens: Record<TokenKind, CachedToken | null> = {
21
+ session: null,
22
+ own: null,
23
+ };
24
+ const inFlightTokens: Record<
25
+ TokenKind,
26
+ { impKey: string; promise: Promise<string> } | null
27
+ > = { session: null, own: null };
19
28
 
20
29
  function readImpersonationKey(): string {
21
30
  if (typeof document === "undefined") return "";
@@ -38,14 +47,16 @@ function readImpersonationKey(): string {
38
47
  }
39
48
 
40
49
  export function clearAuthTokenCache(): void {
41
- cachedToken = null;
42
- inFlight = null;
50
+ cachedTokens.session = null;
51
+ cachedTokens.own = null;
52
+ inFlightTokens.session = null;
53
+ inFlightTokens.own = null;
43
54
  }
44
55
 
45
- async function fetchToken(impKey: string): Promise<string> {
56
+ async function fetchToken(kind: TokenKind, impKey: string): Promise<string> {
46
57
  const res = await fetch("/api/auth/token", {
47
58
  method: "POST",
48
- body: JSON.stringify({}),
59
+ body: JSON.stringify(kind === "own" ? { own: true } : {}),
49
60
  credentials: "include",
50
61
  headers: {
51
62
  "Content-Type": "application/json",
@@ -68,39 +79,57 @@ async function fetchToken(impKey: string): Promise<string> {
68
79
 
69
80
  sessionStorage.removeItem("auth-consent-retried");
70
81
  const bearer = `Bearer ${accessToken}`;
71
- cachedToken = { bearer, expiresOn, impKey };
82
+ cachedTokens[kind] = { bearer, expiresOn, impKey };
72
83
  return bearer;
73
84
  }
74
85
 
75
- async function getFreshToken(): Promise<string> {
76
- const impKey = readImpersonationKey();
86
+ async function getFreshToken(kind: TokenKind): Promise<string> {
87
+ const impKey = kind === "own" ? "" : readImpersonationKey();
77
88
  const now = Date.now() / 1000;
89
+ const cached = cachedTokens[kind];
78
90
 
79
91
  if (
80
- cachedToken &&
81
- cachedToken.impKey === impKey &&
82
- now < cachedToken.expiresOn - REFRESH_MARGIN_SECONDS
92
+ cached &&
93
+ cached.impKey === impKey &&
94
+ now < cached.expiresOn - REFRESH_MARGIN_SECONDS
83
95
  ) {
84
- return cachedToken.bearer;
96
+ return cached.bearer;
85
97
  }
86
98
 
99
+ const inFlight = inFlightTokens[kind];
87
100
  if (inFlight && inFlight.impKey === impKey) {
88
101
  return inFlight.promise;
89
102
  }
90
103
 
91
- const promise = fetchToken(impKey);
92
- inFlight = { impKey, promise };
104
+ const promise = fetchToken(kind, impKey);
105
+ inFlightTokens[kind] = { impKey, promise };
93
106
  promise
94
107
  .catch(() => undefined)
95
108
  .finally(() => {
96
- if (inFlight?.promise === promise) inFlight = null;
109
+ if (inFlightTokens[kind]?.promise === promise)
110
+ inFlightTokens[kind] = null;
97
111
  });
98
112
  return promise;
99
113
  }
100
114
 
115
+ export function fetchOwnToken(): Promise<string> {
116
+ return tokenOrLogin("own");
117
+ }
118
+
119
+ function tokenOrLogin(kind: TokenKind): Promise<string> {
120
+ return getFreshToken(kind).catch((error) => {
121
+ console.error(error);
122
+ const onAuthPage = window.location.pathname.startsWith("/auth/");
123
+ if (onAuthPage) return;
124
+ const reason = error instanceof ConsentRequiredError ? "consent" : "true";
125
+ window.location.href = `/auth/login?error=${reason}`;
126
+ }) as Promise<string>;
127
+ }
128
+
101
129
  export function useAuth() {
102
130
  const { data: rawSession, status, update } = useSession();
103
131
  const { active } = useImpersonation();
132
+ const own = useOwnIdentity();
104
133
 
105
134
  const session = useMemo(() => {
106
135
  if (!active) return rawSession;
@@ -123,15 +152,7 @@ export function useAuth() {
123
152
  signIn,
124
153
  signOut,
125
154
  session,
126
- getToken: () =>
127
- getFreshToken().catch((error) => {
128
- console.error(error);
129
- const onAuthPage = window.location.pathname.startsWith("/auth/");
130
- if (onAuthPage) return;
131
- const reason =
132
- error instanceof ConsentRequiredError ? "consent" : "true";
133
- window.location.href = `/auth/login?error=${reason}`;
134
- }) as Promise<string>,
155
+ getToken: () => tokenOrLogin(own ? "own" : "session"),
135
156
  inProgress: status === "loading",
136
157
  isAuthenticated:
137
158
  status === "authenticated" ||
@@ -139,7 +160,7 @@ export function useAuth() {
139
160
  (status === "loading" && !!session),
140
161
  }),
141
162
 
142
- [update, status, session, active]
163
+ [update, status, session, active, own]
143
164
  );
144
165
 
145
166
  return value;
@@ -0,0 +1,17 @@
1
+ "use client";
2
+
3
+ import { createContext, useContext, type ReactNode } from "react";
4
+
5
+ const OwnIdentityContext = createContext(false);
6
+
7
+ export function OwnIdentity({ children }: { children: ReactNode }) {
8
+ return (
9
+ <OwnIdentityContext.Provider value={true}>
10
+ {children}
11
+ </OwnIdentityContext.Provider>
12
+ );
13
+ }
14
+
15
+ export function useOwnIdentity(): boolean {
16
+ return useContext(OwnIdentityContext);
17
+ }
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useEffect, useMemo, useState } from "react";
4
- import { useAuth } from "../hooks";
4
+ import { fetchOwnToken, useAuth } from "../hooks";
5
5
  import { decodeJwtClaims } from "../token/claims";
6
6
 
7
7
  export const PLATFORM_OPERATOR = "platform-operator";
@@ -20,8 +20,13 @@ function decodeTokenRoles(bearer: string): string[] {
20
20
  return [];
21
21
  }
22
22
 
23
- export function usePlatformRoles(): RolesState {
23
+ export interface PlatformRolesOptions {
24
+ own?: boolean;
25
+ }
26
+
27
+ export function usePlatformRoles(options?: PlatformRolesOptions): RolesState {
24
28
  const { getToken, isAuthenticated } = useAuth();
29
+ const fetchToken = options?.own ? fetchOwnToken : getToken;
25
30
  const [roles, setRoles] = useState<string[] | null>(null);
26
31
 
27
32
  useEffect(() => {
@@ -30,7 +35,7 @@ export function usePlatformRoles(): RolesState {
30
35
  return;
31
36
  }
32
37
  let cancelled = false;
33
- getToken()
38
+ fetchToken()
34
39
  .then((bearer) => {
35
40
  if (!cancelled) setRoles(decodeTokenRoles(bearer));
36
41
  })
@@ -40,7 +45,7 @@ export function usePlatformRoles(): RolesState {
40
45
  return () => {
41
46
  cancelled = true;
42
47
  };
43
- }, [isAuthenticated, getToken]);
48
+ }, [isAuthenticated, fetchToken]);
44
49
 
45
50
  return useMemo(
46
51
  () => ({ roles: roles ?? [], isLoading: roles === null }),
@@ -48,7 +53,10 @@ export function usePlatformRoles(): RolesState {
48
53
  );
49
54
  }
50
55
 
51
- export function useHasPlatformRole(role: string): boolean {
52
- const { roles } = usePlatformRoles();
56
+ export function useHasPlatformRole(
57
+ role: string,
58
+ options?: PlatformRolesOptions
59
+ ): boolean {
60
+ const { roles } = usePlatformRoles(options);
53
61
  return useMemo(() => roles.includes(role), [roles, role]);
54
62
  }
@@ -95,9 +95,18 @@ export async function resolveAccessToken(
95
95
  }
96
96
  }
97
97
 
98
+ async function wantsOwnToken(request: Request): Promise<boolean> {
99
+ const body = (await request.json().catch(() => null)) as {
100
+ own?: unknown;
101
+ } | null;
102
+ return body?.own === true;
103
+ }
104
+
98
105
  export function createTokenHandler(authConfig: Auth.Config) {
99
106
  return async function POST(request: Request) {
100
- const impersonation = readImpersonationSession(request);
107
+ const impersonation = (await wantsOwnToken(request))
108
+ ? null
109
+ : readImpersonationSession(request);
101
110
  if (impersonation && impersonation.expiresOn * 1000 > Date.now()) {
102
111
  return Response.json({
103
112
  accessToken: impersonation.accessToken,