@synqauth/nextjs 0.3.0 → 0.4.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/dist/index.d.ts CHANGED
@@ -109,7 +109,7 @@ interface SynqSignInError {
109
109
  /** Signs in as someone else (`prompt=select_account`), back to the same page. */
110
110
  readonly anotherAccountPath: string;
111
111
  }
112
- /** What a sign-in error code means, in plain words. */
112
+ /** What a sign-in error code means, in plain words: whose side it is on, and what to do. */
113
113
  export declare function signInErrorMessage(code: string): string;
114
114
  //#endregion
115
115
  //#region src/index.d.ts
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as signInErrorMessage, d as memorySessionStore, f as defineSynqPlugin, h as resolveConfig, i as SIGN_IN_ERROR_PARAM, l as createSynqCore, m as normalizePath, n as unknownHostError, o as signInErrorOf, r as unknownHostResponse, t as siteCores, u as keyValueSessionStore } from "./sites-JUnMBDbp.js";
1
+ import { a as signInErrorMessage, d as memorySessionStore, f as defineSynqPlugin, h as resolveConfig, i as SIGN_IN_ERROR_PARAM, l as createSynqCore, m as normalizePath, n as unknownHostError, o as signInErrorOf, r as unknownHostResponse, t as siteCores, u as keyValueSessionStore } from "./sites-BQ8NPcXw.js";
2
2
  import { defineSynqConfig } from "./define.js";
3
3
  import { cookies, headers } from "next/headers.js";
4
4
  import { cache } from "react";
package/dist/proxy.js CHANGED
@@ -1,4 +1,4 @@
1
- import { c as withoutSignInError, h as resolveConfig, i as SIGN_IN_ERROR_PARAM, l as createSynqCore, m as normalizePath, o as signInErrorOf, p as requestJar, r as unknownHostResponse, s as signInErrorPage, t as siteCores } from "./sites-JUnMBDbp.js";
1
+ import { c as withoutSignInError, h as resolveConfig, i as SIGN_IN_ERROR_PARAM, l as createSynqCore, m as normalizePath, o as signInErrorOf, p as requestJar, r as unknownHostResponse, s as signInErrorPage, t as siteCores } from "./sites-BQ8NPcXw.js";
2
2
  import { NextResponse } from "next/server.js";
3
3
  //#region src/proxy.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { SynqError, createSynqClient, isSynqError, memoryStorage, readMethodList, signInMethods } from "@synqauth/js";
1
+ import { SynqError, createSynqClient, isSynqError, memoryStorage, readMethodList, signInEndingOf, signInMethods } from "@synqauth/js";
2
2
  //#region src/crypto.ts
3
3
  const encoder = new TextEncoder();
4
4
  const decoder = new TextDecoder();
@@ -380,7 +380,7 @@ function popupPage(result) {
380
380
  ...result
381
381
  };
382
382
  const data = JSON.stringify(message).replaceAll("<", "\\u003c");
383
- const words = result.ok ? "You’re signed in. You can close this window." : "That didn’t finish. You can close this window and try again.";
383
+ const words = result.ok ? "You’re signed in. You can close this window." : "Sign-in didn’t finish. Close this window and try again from the app.";
384
384
  const html = `<!doctype html>
385
385
  <html lang="en">
386
386
  <head>
@@ -1224,7 +1224,13 @@ function createSynqCore(config) {
1224
1224
  try {
1225
1225
  await client(handle.storage(config.primary, config.primary), config.primary, tx.storage).handleCallback(request.url);
1226
1226
  } catch (error) {
1227
- return failed(isSynqError(error) ? error.code : "server_error");
1227
+ const code = isSynqError(error) ? signInEndingOf(error.description) ?? error.code : "server_error";
1228
+ if (!QUIET_SIGN_IN_ERRORS.has(code)) console.error("@synqauth/nextjs: sign-in callback failed", {
1229
+ code,
1230
+ status: isSynqError(error) ? error.status : void 0,
1231
+ requestId: isSynqError(error) ? error.requestId : void 0
1232
+ });
1233
+ return failed(code);
1228
1234
  }
1229
1235
  if (tx.found.reauthenticate !== void 0) {
1230
1236
  const refused = await reauthenticationRefused(jar, handle, tx.found.reauthenticate);
@@ -1737,7 +1743,10 @@ function createSynqCore(config) {
1737
1743
  const rest = url.pathname.slice(prefix.length);
1738
1744
  const [first = "", ...tail] = rest.split("/");
1739
1745
  const jar = requestJar(request);
1740
- if (!["GET", "HEAD"].includes(request.method) && !sameOrigin(request, config.appUrl)) return problem(403, "forbidden", "A change must come from the app's own pages");
1746
+ if (!["GET", "HEAD"].includes(request.method) && !sameOrigin(request, config.appUrl)) {
1747
+ reportOriginMismatch(request, config.appUrl);
1748
+ return problem(403, "origin_mismatch", "This change came from another address than the app's own pages (appUrl)");
1749
+ }
1741
1750
  let response;
1742
1751
  try {
1743
1752
  if (rest === "sign-in" && request.method === "GET") response = await signIn(request, jar);
@@ -1760,6 +1769,12 @@ function createSynqCore(config) {
1760
1769
  else if (!RESERVED.has(first)) response = await plugin(request, jar, first, tail.join("/"));
1761
1770
  } catch (error) {
1762
1771
  if (!isSynqError(error)) throw error;
1772
+ if (error.status === void 0 || error.status >= 500) console.error("@synqauth/nextjs: a route under the mount failed", {
1773
+ route: rest,
1774
+ code: error.code,
1775
+ status: error.status,
1776
+ requestId: error.requestId
1777
+ });
1763
1778
  response = problem(error.status && error.status >= 400 ? 502 : 500, error.code, error.message);
1764
1779
  }
1765
1780
  response ??= problem(404, "not_found", "No such route under the mount");
@@ -1843,6 +1858,30 @@ function sameOrigin(request, appUrl) {
1843
1858
  if (origin) return origin === appUrl.origin;
1844
1859
  return request.headers.get("sec-fetch-site") === "same-origin";
1845
1860
  }
1861
+ /**
1862
+ * Sign-in errors that are the person's own doing or an expected step, which
1863
+ * no server log needs to hear of (AERO-2757).
1864
+ */
1865
+ const QUIET_SIGN_IN_ERRORS = /* @__PURE__ */ new Set([
1866
+ "access_denied",
1867
+ "login_required",
1868
+ "interaction_required",
1869
+ "consent_required",
1870
+ "account_selection_required",
1871
+ "cancelled",
1872
+ "consent_denied",
1873
+ "token_gate_refused",
1874
+ "passkey_required",
1875
+ "unknown_state"
1876
+ ]);
1877
+ let originMismatchReported = false;
1878
+ /** Says once per process which origin the mount refused, and what appUrl says (AERO-2757). */
1879
+ function reportOriginMismatch(request, appUrl) {
1880
+ if (originMismatchReported) return;
1881
+ originMismatchReported = true;
1882
+ const origin = request.headers.get("origin") ?? request.headers.get("sec-fetch-site") ?? "unknown";
1883
+ console.error(`@synqauth/nextjs: refused a change from ${origin}: appUrl is ${appUrl.origin}. Set appUrl (or SYNQ_APP_URL) to the origin people use.`);
1884
+ }
1846
1885
  function withError(target, code) {
1847
1886
  const url = new URL(target);
1848
1887
  url.searchParams.set("synq_error", code);
@@ -1884,19 +1923,47 @@ const SIGN_IN_ERROR_PARAM = "synq_error";
1884
1923
  /** An OAuth or SDK error code, as the route mount puts it there. */
1885
1924
  const CODE = /^[a-z0-9_]{1,64}$/;
1886
1925
  const MESSAGES = {
1887
- access_denied: "The sign-in was cancelled, or this account can’t use this app.",
1926
+ access_denied: "This account can’t use this app, or the sign-in was canceled. Sign in again, or use another account.",
1927
+ cancelled: "You canceled signing in. Sign in when you’re ready.",
1928
+ consent_denied: "You didn’t allow this app to use your account. Sign in again to allow it.",
1929
+ token_gate_refused: "This account’s wallets don’t hold what this app asks for. Use another account, or add the wallet that holds it.",
1930
+ token_gate_unavailable: "We couldn’t check your wallets. That’s a problem on Synq’s side, not yours. Try again in a minute.",
1931
+ passkey_required: "This app needs you to sign in with a passkey.",
1932
+ unmet_authentication_requirements: "This app needs you to sign in with a passkey.",
1933
+ level_not_met: "This app needs you to sign in with a passkey.",
1888
1934
  login_required: "You need to sign in to continue.",
1889
1935
  interaction_required: "You need to sign in to continue.",
1890
1936
  consent_required: "You need to allow this app to use your account to continue.",
1891
1937
  account_selection_required: "Choose an account to continue.",
1892
- temporarily_unavailable: "Signing in isn’t available just now. Try again in a moment.",
1893
- unknown_state: "This sign-in expired, or it finished in another tab. Try again.",
1938
+ temporarily_unavailable: "Signing in is down for a moment on Synq’s side. Try again in a minute.",
1939
+ server_error: "Synq had a problem finishing your sign-in. Try again in a minute.",
1940
+ invalid_grant: "This sign-in expired or was already used. Sign in again.",
1941
+ unknown_state: "This sign-in expired, or it finished in another tab. Sign in again.",
1894
1942
  other_account: "That signed in to another account. Confirm with a way in of the account signed in here.",
1895
1943
  not_signed_in: "You were signed out. Sign in again."
1896
1944
  };
1897
- /** What a sign-in error code means, in plain words. */
1945
+ /**
1946
+ * The app's own setup, which only its team can fix: retrying gets the same
1947
+ * answer, and nothing the person does changes it.
1948
+ */
1949
+ const APP_SETUP = /* @__PURE__ */ new Set([
1950
+ "invalid_client",
1951
+ "unauthorized_client",
1952
+ "invalid_scope",
1953
+ "invalid_request",
1954
+ "invalid_response",
1955
+ "invalid_redirect_uri",
1956
+ "unsupported_response_type",
1957
+ "discovery_failed",
1958
+ "method_not_offered",
1959
+ "origin_mismatch"
1960
+ ]);
1961
+ /** What a sign-in error code means, in plain words: whose side it is on, and what to do. */
1898
1962
  function signInErrorMessage(code) {
1899
- return MESSAGES[code] ?? "Something went wrong signing in. Try again.";
1963
+ const said = MESSAGES[code];
1964
+ if (said) return said;
1965
+ if (APP_SETUP.has(code)) return `This app’s sign-in isn’t set up right, so it can’t finish. It’s not something you did: tell the app’s team (code ${code}).`;
1966
+ return `Your sign-in didn’t finish (code ${code}). Try again, and if it keeps happening, tell the app’s team.`;
1900
1967
  }
1901
1968
  /** A path and query on the app, without `synq_error`: where the ways on come back to. */
1902
1969
  function withoutSignInError(pathAndSearch) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synqauth/nextjs",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Synq for Next.js: a backend-for-frontend session, so the browser never holds a Synq token.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -39,7 +39,7 @@
39
39
  "access": "public"
40
40
  },
41
41
  "dependencies": {
42
- "@synqauth/js": "0.3.0"
42
+ "@synqauth/js": "0.4.0"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "next": ">=16.0.0",