@snokam/auth 4.0.0 → 5.0.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/CHANGELOG.md CHANGED
@@ -3,6 +3,75 @@
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.0.0](https://github.com/snokam/components/compare/@snokam/auth@3.7.14...@snokam/auth@5.0.0) (2026-10-14)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **auth:** verify a password once when TOTP enrolment is pending, and say when sign-in is slow ([#2893](https://github.com/snokam/components/issues/2893)) ([d3e5c79](https://github.com/snokam/components/commit/d3e5c79c4c22db49389cddd07e1331dc6d25c381))
11
+
12
+ ### Features
13
+
14
+ - per-company branding (logo, dark logo, icon) ([#2907](https://github.com/snokam/components/issues/2907)) ([dc32583](https://github.com/snokam/components/commit/dc325832803c88158937f45fe5b9e1b64846a52d))
15
+ - persona mode per business, prs claim, Vis som gate, handoff sealing and CI persona (test identity P1) ([#2894](https://github.com/snokam/components/issues/2894)) ([8ab98a8](https://github.com/snokam/components/commit/8ab98a846ed6a5f962eadca6723c923a55395ce1)), closes [#2890](https://github.com/snokam/components/issues/2890)
16
+ - **platform:** a test persona carries the operator's own operator role ([#2913](https://github.com/snokam/components/issues/2913)) ([0d777a3](https://github.com/snokam/components/commit/0d777a39400cace619513e77ba0872ffeb56f7c2))
17
+
18
+ ### BREAKING CHANGES
19
+
20
+ - SimpleHeaderBase and FooterBase take `logo` as a ReactNode;
21
+ the HeaderLogo and FooterLogo types are removed.
22
+
23
+ Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
24
+ Claude-Session: https://claude.ai/code/session_01Nv57F4rwzYRkuUVFdd2wXA
25
+
26
+ - feat(platform-web): company branding in /manage, at login and in the shell
27
+
28
+ /manage/innstillinger gets a Merkevare section: the logo for light
29
+ backgrounds, the logo for dark backgrounds and the square icon, each with a
30
+ preview on its own background, upload (react-hook-form + zod: type and 2 MB
31
+ checked before sending) and remove. Server error codes map to CMS copy.
32
+ The central login shows the company's logo above "Logg inn hos …", from
33
+ login-config. The /manage shell shows the managed business's logo linking to
34
+ its front page; /admin and an operator without a business show the platform
35
+ business. The public snosky.no chrome keeps the Snøsky logo from the platform
36
+ CMS, through the same BusinessLogo.
37
+
38
+ Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
39
+ Claude-Session: https://claude.ai/code/session_01Nv57F4rwzYRkuUVFdd2wXA
40
+
41
+ - fix(my-page-web): drop leftover Snøkam favicons
42
+
43
+ The unused pages-router meta component and finance-web's public/favicon.ico
44
+ served Snøkam's icon to every company; favicons now come from the
45
+ company's uploaded icon.
46
+
47
+ Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
48
+ Claude-Session: https://claude.ai/code/session_01Nv57F4rwzYRkuUVFdd2wXA
49
+
50
+ - fix(handbook-web): no Snøkam share image, no raw-URL fallback in branding metadata
51
+
52
+ The handbook's root metadata no longer hardcodes the Snøkam icon as the
53
+ share image, so the company's share image from makeTenantMetadata applies.
54
+ The favicon and share helpers drop their raw-URL fallback: a transform that
55
+ yields nothing yields no image, never an untransformed (possibly SVG) URL.
56
+
57
+ Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
58
+ Claude-Session: https://claude.ai/code/session_01Nv57F4rwzYRkuUVFdd2wXA
59
+
60
+ - fix(ui): show the light logo inverted on dark when no dark logo is uploaded
61
+
62
+ Without a dark logo, a dark background shows the light logo turned white
63
+ (brightness(0) invert(1)) instead of the wordmark. The Merkevare previews
64
+ use static brand tokens, and upload errors are read by parsing the JSON
65
+ body rather than stripping quotes.
66
+
67
+ Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
68
+ Claude-Session: https://claude.ai/code/session_01Nv57F4rwzYRkuUVFdd2wXA
69
+
70
+ - chore(clients): regenerate platform-function clients for branding
71
+
72
+ Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
73
+ Claude-Session: https://claude.ai/code/session_01Nv57F4rwzYRkuUVFdd2wXA
74
+
6
75
  # [4.0.0](https://github.com/snokam/components/compare/@snokam/auth@3.7.14...@snokam/auth@4.0.0) (2026-10-14)
7
76
 
8
77
  ### Bug Fixes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@snokam/auth",
3
3
  "description": "Snøkam auth components",
4
- "version": "4.0.0",
4
+ "version": "5.0.0",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
7
7
  "files": [
@@ -27,11 +27,11 @@
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": "4.0.0",
30
+ "@snokam/platform-client": "5.0.0",
31
31
  "@snokam/react-query": "1.1.0",
32
- "@snokam/sanity-page-content": "5.0.0",
33
- "@snokam/ui": "5.0.0",
34
- "@snokam/tenant": "3.0.0"
32
+ "@snokam/sanity-page-content": "6.0.0",
33
+ "@snokam/tenant": "4.0.0",
34
+ "@snokam/ui": "6.0.0"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "next": "16.3.8",
@@ -0,0 +1,29 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import { claimedRoles, tokenClaims } from "./claims.ts";
4
+
5
+ function token(payload: object): string {
6
+ const encoded = Buffer.from(JSON.stringify(payload)).toString("base64url");
7
+ return `header.${encoded}.signature`;
8
+ }
9
+
10
+ describe("roles carried by a view-as token", () => {
11
+ it("reads the actor and a single role", () => {
12
+ const claims = tokenClaims(
13
+ token({
14
+ act: "morten.jansrud@test.snokam.no",
15
+ roles: "platform-operator",
16
+ })
17
+ );
18
+ assert.equal(claims.act, "morten.jansrud@test.snokam.no");
19
+ assert.deepEqual(claimedRoles(claims), ["platform-operator"]);
20
+ });
21
+
22
+ it("reads several roles and none", () => {
23
+ assert.deepEqual(claimedRoles(tokenClaims(token({ roles: ["a", "b"] }))), [
24
+ "a",
25
+ "b",
26
+ ]);
27
+ assert.deepEqual(claimedRoles(tokenClaims(token({}))), []);
28
+ });
29
+ });
@@ -4,6 +4,12 @@ export interface TokenClaims {
4
4
  act?: string;
5
5
  imp?: string;
6
6
  prs?: string;
7
+ roles?: string | string[];
8
+ }
9
+
10
+ export function claimedRoles(claims: TokenClaims): string[] {
11
+ if (!claims.roles) return [];
12
+ return Array.isArray(claims.roles) ? claims.roles : [claims.roles];
7
13
  }
8
14
 
9
15
  export function tokenClaims(accessToken: string): TokenClaims {
@@ -1,4 +1,5 @@
1
1
  import { resolveCookieDomain } from "../session";
2
+ import { claimedRoles, tokenClaims } from "./claims";
2
3
 
3
4
  export const IMP_SESSION_COOKIE = "imp_session";
4
5
  export const IMP_ACTIVE_COOKIE = "imp_active";
@@ -17,6 +18,8 @@ export interface ImpersonationActive {
17
18
  tokenId: string;
18
19
  expiresOn: number;
19
20
  returnTo?: string;
21
+ actorEmail?: string;
22
+ roles?: string[];
20
23
  }
21
24
 
22
25
  interface MintedToken {
@@ -97,7 +100,10 @@ export function buildImpersonationCookies(
97
100
  expiresOn: minted.expiresOn,
98
101
  tokenId: minted.tokenId,
99
102
  };
103
+ const claims = tokenClaims(minted.accessToken);
100
104
  const active: ImpersonationActive = {
105
+ actorEmail: claims.act,
106
+ roles: claimedRoles(claims),
101
107
  targetEmail: minted.targetEmail,
102
108
  targetName: minted.targetName,
103
109
  targetImage: minted.targetImage,