@snokam/auth 3.7.14 → 3.7.15

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,12 @@
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
+ ## [3.7.15](https://github.com/snokam/components/compare/@snokam/auth@3.7.14...@snokam/auth@3.7.15) (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
+
6
12
  ## [3.7.14](https://github.com/snokam/components/compare/@snokam/auth@3.7.13...@snokam/auth@3.7.14) (2026-10-14)
7
13
 
8
14
  **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": "3.7.14",
4
+ "version": "3.7.15",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
7
7
  "files": [
@@ -26,12 +26,12 @@
26
26
  "@snokam/employees-client": "^1.3.0",
27
27
  "next-auth": "4.24.15",
28
28
  "path-to-regexp": "^8.4.2",
29
- "@snokam/identity": "1.1.0",
30
29
  "@snokam/platform-client": "3.8.0",
31
30
  "@snokam/react-query": "1.1.0",
32
- "@snokam/ui": "4.0.3",
31
+ "@snokam/sanity-page-content": "4.11.1",
32
+ "@snokam/identity": "1.1.0",
33
33
  "@snokam/tenant": "2.1.17",
34
- "@snokam/sanity-page-content": "4.11.1"
34
+ "@snokam/ui": "4.0.3"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "next": "16.3.8",
@@ -3,7 +3,11 @@ import AzureADProvider from "next-auth/providers/azure-ad";
3
3
  import CredentialsProvider from "next-auth/providers/credentials";
4
4
  import { getDefaultAuthParams } from "../options/params";
5
5
  import { openHandoff } from "../handoff/ticket";
6
- import { passwordLogin, redeemBootstrapToken } from "../password/api";
6
+ import {
7
+ enrolmentRequiredError,
8
+ passwordLogin,
9
+ redeemBootstrapToken,
10
+ } from "../password/api";
7
11
  import { requestClientIp, requestHost } from "./request";
8
12
  import { Auth } from "../types";
9
13
 
@@ -100,6 +104,8 @@ function password() {
100
104
  },
101
105
  requestClientIp(req)
102
106
  );
107
+ if (outcome.enrolment)
108
+ throw new Error(enrolmentRequiredError(outcome.enrolment));
103
109
  if (!outcome.login)
104
110
  throw new Error(outcome.error ?? "invalid_credentials");
105
111
 
@@ -1,3 +1,5 @@
1
+ export { enrolmentFromError, enrolmentRequiredError } from "./enrolment";
2
+
1
3
  export interface PasswordLoginResult {
2
4
  userId: string;
3
5
  businessId: string;
@@ -0,0 +1,24 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import { enrolmentFromError, enrolmentRequiredError } from "./enrolment.ts";
4
+
5
+ const enrolment = {
6
+ email: "owner@businessx.no",
7
+ enrolmentToken: "token+/=",
8
+ otpauthUri: "otpauth://totp/Sn%C3%B8sky:owner?secret=ABC&issuer=Sn%C3%B8sky",
9
+ };
10
+
11
+ describe("totp enrolment carried by the sign-in error", () => {
12
+ it("survives the round trip through the sign-in error url", () => {
13
+ const url = new URL("https://login.test.snosky.no/auth/login");
14
+ url.searchParams.set("error", enrolmentRequiredError(enrolment));
15
+ const error = new URL(url.toString()).searchParams.get("error");
16
+ assert.deepEqual(enrolmentFromError(error), enrolment);
17
+ });
18
+
19
+ it("ignores every other sign-in error", () => {
20
+ assert.equal(enrolmentFromError("invalid_credentials"), null);
21
+ assert.equal(enrolmentFromError("totp_enrollment_required"), null);
22
+ assert.equal(enrolmentFromError(undefined), null);
23
+ });
24
+ });
@@ -0,0 +1,15 @@
1
+ import type { PasswordEnrolment } from "./api";
2
+
3
+ const ENROLMENT_REQUIRED = "totp_enrollment_required";
4
+ const PREFIX = `${ENROLMENT_REQUIRED}:`;
5
+
6
+ export function enrolmentRequiredError(enrolment: PasswordEnrolment): string {
7
+ return `${PREFIX}${JSON.stringify(enrolment)}`;
8
+ }
9
+
10
+ export function enrolmentFromError(
11
+ error: string | undefined | null
12
+ ): PasswordEnrolment | null {
13
+ if (!error?.startsWith(PREFIX)) return null;
14
+ return JSON.parse(error.slice(PREFIX.length)) as PasswordEnrolment;
15
+ }