@spfn/auth 0.2.0-beta.86 → 0.2.0-beta.87

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/server.js CHANGED
@@ -1379,7 +1379,7 @@ var init_boolean2 = __esm({
1379
1379
  });
1380
1380
 
1381
1381
  // ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/bigint/bigint.mjs
1382
- function BigInt(options) {
1382
+ function BigInt2(options) {
1383
1383
  return CreateType({ [Kind]: "BigInt", type: "bigint" }, options);
1384
1384
  }
1385
1385
  var init_bigint = __esm({
@@ -1441,7 +1441,7 @@ var init_string2 = __esm({
1441
1441
  // ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/template-literal/syntax.mjs
1442
1442
  function* FromUnion(syntax) {
1443
1443
  const trim = syntax.trim().replace(/"|'/g, "");
1444
- return trim === "boolean" ? yield Boolean2() : trim === "number" ? yield Number2() : trim === "bigint" ? yield BigInt() : trim === "string" ? yield String2() : yield (() => {
1444
+ return trim === "boolean" ? yield Boolean2() : trim === "number" ? yield Number2() : trim === "bigint" ? yield BigInt2() : trim === "string" ? yield String2() : yield (() => {
1445
1445
  const literals = trim.split("|").map((literal) => Literal(literal.trim()));
1446
1446
  return literals.length === 0 ? Never() : literals.length === 1 ? literals[0] : UnionEvaluated(literals);
1447
1447
  })();
@@ -2457,7 +2457,7 @@ function ConditionalReadonly(T, root) {
2457
2457
  return root === true ? T : Readonly(T);
2458
2458
  }
2459
2459
  function FromValue(value, root) {
2460
- return IsAsyncIterator(value) ? ConditionalReadonly(Any(), root) : IsIterator(value) ? ConditionalReadonly(Any(), root) : IsArray(value) ? Readonly(Tuple(FromArray3(value))) : IsUint8Array(value) ? Uint8Array2() : IsDate(value) ? Date2() : IsObject(value) ? ConditionalReadonly(Object2(FromProperties7(value)), root) : IsFunction(value) ? ConditionalReadonly(Function([], Unknown()), root) : IsUndefined(value) ? Undefined() : IsNull(value) ? Null() : IsSymbol(value) ? Symbol2() : IsBigInt(value) ? BigInt() : IsNumber(value) ? Literal(value) : IsBoolean(value) ? Literal(value) : IsString(value) ? Literal(value) : Object2({});
2460
+ return IsAsyncIterator(value) ? ConditionalReadonly(Any(), root) : IsIterator(value) ? ConditionalReadonly(Any(), root) : IsArray(value) ? Readonly(Tuple(FromArray3(value))) : IsUint8Array(value) ? Uint8Array2() : IsDate(value) ? Date2() : IsObject(value) ? ConditionalReadonly(Object2(FromProperties7(value)), root) : IsFunction(value) ? ConditionalReadonly(Function([], Unknown()), root) : IsUndefined(value) ? Undefined() : IsNull(value) ? Null() : IsSymbol(value) ? Symbol2() : IsBigInt(value) ? BigInt2() : IsNumber(value) ? Literal(value) : IsBoolean(value) ? Literal(value) : IsString(value) ? Literal(value) : Object2({});
2461
2461
  }
2462
2462
  function Const(T, options) {
2463
2463
  return CreateType(FromValue(T, true), options);
@@ -4243,7 +4243,7 @@ __export(type_exports3, {
4243
4243
  Array: () => Array2,
4244
4244
  AsyncIterator: () => AsyncIterator,
4245
4245
  Awaited: () => Awaited,
4246
- BigInt: () => BigInt,
4246
+ BigInt: () => BigInt2,
4247
4247
  Boolean: () => Boolean2,
4248
4248
  Capitalize: () => Capitalize,
4249
4249
  Composite: () => Composite,
@@ -5802,6 +5802,17 @@ var init_keys_repository = __esm({
5802
5802
  ).returning();
5803
5803
  return result[0] ?? null;
5804
5804
  }
5805
+ /**
5806
+ * Key ID로 공개키 조회 — 활성 여부 무관 (clientProofV1 admission의 revocation 판정용)
5807
+ *
5808
+ * 폐기(isActive=false)·만료(expiresAt 경과)를 SESSION_REVOKED로, 미등록을
5809
+ * PROOF_INVALID로 구분해야 하므로 활성 필터 없이 조회한다. keyId는 UNIQUE.
5810
+ * Read replica 사용
5811
+ */
5812
+ async findByKeyId(keyId) {
5813
+ const result = await this.readDb.select().from(userPublicKeys).where(eq2(userPublicKeys.keyId, keyId)).limit(1);
5814
+ return result[0] ?? null;
5815
+ }
5805
5816
  /**
5806
5817
  * Key ID로 활성 공개키 조회 (authenticate용)
5807
5818
  * Read replica 사용
@@ -8158,12 +8169,15 @@ async function verifyCodeService(params) {
8158
8169
  };
8159
8170
  }
8160
8171
 
8172
+ // src/server/lib/key-policy.ts
8173
+ var KEY_TTL_DAYS = 90;
8174
+
8161
8175
  // src/server/services/key.service.ts
8162
8176
  init_repositories();
8163
8177
  import { InvalidKeyFingerprintError } from "@spfn/auth/errors";
8164
8178
  function getKeyExpiryDate() {
8165
8179
  const expiresAt = /* @__PURE__ */ new Date();
8166
- expiresAt.setDate(expiresAt.getDate() + 90);
8180
+ expiresAt.setDate(expiresAt.getDate() + KEY_TTL_DAYS);
8167
8181
  return expiresAt;
8168
8182
  }
8169
8183
  async function registerPublicKeyService(params) {
@@ -9750,8 +9764,8 @@ function getGithubConfig() {
9750
9764
  };
9751
9765
  }
9752
9766
  function getGithubScopes() {
9753
- const configured = env3.SPFN_AUTH_GITHUB_SCOPES;
9754
- return configured ? configured.split(",").map((scope) => scope.trim()).filter(Boolean) : ["read:user", "user:email"];
9767
+ const configured2 = env3.SPFN_AUTH_GITHUB_SCOPES;
9768
+ return configured2 ? configured2.split(",").map((scope) => scope.trim()).filter(Boolean) : ["read:user", "user:email"];
9755
9769
  }
9756
9770
  async function requestGithubTokens(params) {
9757
9771
  const response = await fetch(GITHUB_TOKEN_URL, {
@@ -9881,8 +9895,8 @@ function getKakaoConfig() {
9881
9895
  };
9882
9896
  }
9883
9897
  function getKakaoScopes() {
9884
- const configured = env3.SPFN_AUTH_KAKAO_SCOPES;
9885
- return configured ? configured.split(",").map((scope) => scope.trim()).filter(Boolean) : ["account_email"];
9898
+ const configured2 = env3.SPFN_AUTH_KAKAO_SCOPES;
9899
+ return configured2 ? configured2.split(",").map((scope) => scope.trim()).filter(Boolean) : ["account_email"];
9886
9900
  }
9887
9901
  async function requestKakaoTokens(params) {
9888
9902
  const response = await fetch(KAKAO_TOKEN_URL, {
@@ -10685,33 +10699,831 @@ import { EMAIL_PATTERN as EMAIL_PATTERN2, UUID_PATTERN } from "@spfn/auth";
10685
10699
 
10686
10700
  // src/server/middleware/authenticate.ts
10687
10701
  import { defineMiddleware } from "@spfn/core/route";
10688
- import { UnauthorizedError } from "@spfn/core/errors";
10689
- import { verifyClientToken as verifyClientToken2, decodeToken as decodeToken2, authLogger as authLogger2, keysRepository as keysRepository2, usersRepository as usersRepository2, userProfilesRepository as userProfilesRepository2, getPendingDeletionInfo as getPendingDeletionInfo2 } from "@spfn/auth/server";
10702
+ import { UnauthorizedError as UnauthorizedError2 } from "@spfn/core/errors";
10703
+ import { verifyClientToken as verifyClientToken2, decodeToken as decodeToken2, authLogger as authLogger3, keysRepository as keysRepository3, usersRepository as usersRepository3, userProfilesRepository as userProfilesRepository3 } from "@spfn/auth/server";
10690
10704
  import {
10691
10705
  InvalidTokenError,
10692
10706
  TokenExpiredError,
10693
- KeyExpiredError,
10694
- AccountDisabledError as AccountDisabledError3,
10695
- AccountPendingDeletionError as AccountPendingDeletionError3
10707
+ KeyExpiredError
10696
10708
  } from "@spfn/auth/errors";
10709
+
10710
+ // src/server/middleware/auth-profiles.ts
10711
+ import {
10712
+ BadRequestError as BadRequestError2,
10713
+ ConflictError as ConflictError2,
10714
+ ServiceUnavailableError,
10715
+ UnauthorizedError
10716
+ } from "@spfn/core/errors";
10717
+ import {
10718
+ authLogger as authLogger2,
10719
+ keysRepository as keysRepository2,
10720
+ usersRepository as usersRepository2,
10721
+ userProfilesRepository as userProfilesRepository2,
10722
+ getPendingDeletionInfo as getPendingDeletionInfo2
10723
+ } from "@spfn/auth/server";
10724
+ import { AccountDisabledError as AccountDisabledError3, AccountPendingDeletionError as AccountPendingDeletionError3 } from "@spfn/auth/errors";
10725
+
10726
+ // src/server/client-proof/canonical-json.ts
10727
+ var CanonicalJsonError = class extends Error {
10728
+ constructor(code) {
10729
+ super(`canonical JSON: ${code}`);
10730
+ this.code = code;
10731
+ this.name = "CanonicalJsonError";
10732
+ }
10733
+ };
10734
+ var INT64_MIN = -(2n ** 63n);
10735
+ var INT64_MAX = 2n ** 63n - 1n;
10736
+ function parseCanonicalJson(bytes) {
10737
+ let text12;
10738
+ try {
10739
+ text12 = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
10740
+ } catch {
10741
+ throw new CanonicalJsonError("INVALID_UTF8");
10742
+ }
10743
+ const parser = new Parser(text12);
10744
+ const value = parser.parseValue();
10745
+ parser.skipWhitespace();
10746
+ if (!parser.atEnd()) {
10747
+ throw new CanonicalJsonError("TRAILING_CONTENT");
10748
+ }
10749
+ return value;
10750
+ }
10751
+ function isCanonicalBytes(bytes, value) {
10752
+ const encoded = encodeCanonicalJson(value);
10753
+ if (encoded.length !== bytes.length) {
10754
+ return false;
10755
+ }
10756
+ for (let i = 0; i < encoded.length; i++) {
10757
+ if (encoded[i] !== bytes[i]) {
10758
+ return false;
10759
+ }
10760
+ }
10761
+ return true;
10762
+ }
10763
+ var Parser = class {
10764
+ constructor(text12) {
10765
+ this.text = text12;
10766
+ }
10767
+ pos = 0;
10768
+ atEnd() {
10769
+ return this.pos >= this.text.length;
10770
+ }
10771
+ skipWhitespace() {
10772
+ while (!this.atEnd()) {
10773
+ const c = this.text[this.pos];
10774
+ if (c === " " || c === " " || c === "\n" || c === "\r") {
10775
+ this.pos++;
10776
+ continue;
10777
+ }
10778
+ break;
10779
+ }
10780
+ }
10781
+ parseValue() {
10782
+ this.skipWhitespace();
10783
+ if (this.atEnd()) {
10784
+ throw new CanonicalJsonError("UNEXPECTED_END");
10785
+ }
10786
+ const c = this.text[this.pos];
10787
+ if (c === "{") {
10788
+ return this.parseObject();
10789
+ }
10790
+ if (c === "[") {
10791
+ return this.parseArray();
10792
+ }
10793
+ if (c === '"') {
10794
+ return this.parseString();
10795
+ }
10796
+ if (c === "-" || c >= "0" && c <= "9") {
10797
+ return this.parseNumber();
10798
+ }
10799
+ if (this.text.startsWith("null", this.pos)) {
10800
+ this.pos += 4;
10801
+ return null;
10802
+ }
10803
+ if (this.text.startsWith("true", this.pos)) {
10804
+ this.pos += 4;
10805
+ return true;
10806
+ }
10807
+ if (this.text.startsWith("false", this.pos)) {
10808
+ this.pos += 5;
10809
+ return false;
10810
+ }
10811
+ throw new CanonicalJsonError("INVALID_TOKEN");
10812
+ }
10813
+ parseObject() {
10814
+ this.pos++;
10815
+ const members = /* @__PURE__ */ new Map();
10816
+ this.skipWhitespace();
10817
+ if (this.atEnd()) {
10818
+ throw new CanonicalJsonError("UNEXPECTED_END");
10819
+ }
10820
+ if (this.text[this.pos] === "}") {
10821
+ this.pos++;
10822
+ return members;
10823
+ }
10824
+ for (; ; ) {
10825
+ this.skipWhitespace();
10826
+ if (this.atEnd()) {
10827
+ throw new CanonicalJsonError("UNEXPECTED_END");
10828
+ }
10829
+ if (this.text[this.pos] !== '"') {
10830
+ throw new CanonicalJsonError("INVALID_TOKEN");
10831
+ }
10832
+ const key = this.parseString();
10833
+ if (members.has(key)) {
10834
+ throw new CanonicalJsonError("DUPLICATE_KEY");
10835
+ }
10836
+ this.skipWhitespace();
10837
+ if (this.atEnd()) {
10838
+ throw new CanonicalJsonError("UNEXPECTED_END");
10839
+ }
10840
+ if (this.text[this.pos] !== ":") {
10841
+ throw new CanonicalJsonError("INVALID_TOKEN");
10842
+ }
10843
+ this.pos++;
10844
+ members.set(key, this.parseValue());
10845
+ this.skipWhitespace();
10846
+ if (this.atEnd()) {
10847
+ throw new CanonicalJsonError("UNEXPECTED_END");
10848
+ }
10849
+ const next = this.text[this.pos];
10850
+ if (next === ",") {
10851
+ this.pos++;
10852
+ continue;
10853
+ }
10854
+ if (next === "}") {
10855
+ this.pos++;
10856
+ return members;
10857
+ }
10858
+ throw new CanonicalJsonError("INVALID_TOKEN");
10859
+ }
10860
+ }
10861
+ parseArray() {
10862
+ this.pos++;
10863
+ const items = [];
10864
+ this.skipWhitespace();
10865
+ if (this.atEnd()) {
10866
+ throw new CanonicalJsonError("UNEXPECTED_END");
10867
+ }
10868
+ if (this.text[this.pos] === "]") {
10869
+ this.pos++;
10870
+ return items;
10871
+ }
10872
+ for (; ; ) {
10873
+ items.push(this.parseValue());
10874
+ this.skipWhitespace();
10875
+ if (this.atEnd()) {
10876
+ throw new CanonicalJsonError("UNEXPECTED_END");
10877
+ }
10878
+ const next = this.text[this.pos];
10879
+ if (next === ",") {
10880
+ this.pos++;
10881
+ continue;
10882
+ }
10883
+ if (next === "]") {
10884
+ this.pos++;
10885
+ return items;
10886
+ }
10887
+ throw new CanonicalJsonError("INVALID_TOKEN");
10888
+ }
10889
+ }
10890
+ parseString() {
10891
+ this.pos++;
10892
+ let out = "";
10893
+ for (; ; ) {
10894
+ if (this.atEnd()) {
10895
+ throw new CanonicalJsonError("UNEXPECTED_END");
10896
+ }
10897
+ const c = this.text[this.pos];
10898
+ const code = this.text.charCodeAt(this.pos);
10899
+ if (c === '"') {
10900
+ this.pos++;
10901
+ return out;
10902
+ }
10903
+ if (c === "\\") {
10904
+ out += this.parseEscape();
10905
+ continue;
10906
+ }
10907
+ if (code < 32) {
10908
+ throw new CanonicalJsonError("INVALID_TOKEN");
10909
+ }
10910
+ out += c;
10911
+ this.pos++;
10912
+ }
10913
+ }
10914
+ parseEscape() {
10915
+ this.pos++;
10916
+ if (this.atEnd()) {
10917
+ throw new CanonicalJsonError("UNEXPECTED_END");
10918
+ }
10919
+ const c = this.text[this.pos];
10920
+ this.pos++;
10921
+ switch (c) {
10922
+ case '"':
10923
+ return '"';
10924
+ case "\\":
10925
+ return "\\";
10926
+ case "/":
10927
+ return "/";
10928
+ case "b":
10929
+ return "\b";
10930
+ case "f":
10931
+ return "\f";
10932
+ case "n":
10933
+ return "\n";
10934
+ case "r":
10935
+ return "\r";
10936
+ case "t":
10937
+ return " ";
10938
+ case "u":
10939
+ return this.parseUnicodeEscape();
10940
+ default:
10941
+ throw new CanonicalJsonError("INVALID_ESCAPE");
10942
+ }
10943
+ }
10944
+ parseUnicodeEscape() {
10945
+ const high = this.readHex4();
10946
+ if (high >= 56320 && high <= 57343) {
10947
+ throw new CanonicalJsonError("INVALID_ESCAPE");
10948
+ }
10949
+ if (high < 55296 || high > 56319) {
10950
+ return String.fromCharCode(high);
10951
+ }
10952
+ if (this.text[this.pos] !== "\\" || this.text[this.pos + 1] !== "u") {
10953
+ throw new CanonicalJsonError("INVALID_ESCAPE");
10954
+ }
10955
+ this.pos += 2;
10956
+ const low = this.readHex4();
10957
+ if (low < 56320 || low > 57343) {
10958
+ throw new CanonicalJsonError("INVALID_ESCAPE");
10959
+ }
10960
+ return String.fromCharCode(high, low);
10961
+ }
10962
+ readHex4() {
10963
+ if (this.pos + 4 > this.text.length) {
10964
+ throw new CanonicalJsonError("UNEXPECTED_END");
10965
+ }
10966
+ const hex = this.text.slice(this.pos, this.pos + 4);
10967
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
10968
+ throw new CanonicalJsonError("INVALID_ESCAPE");
10969
+ }
10970
+ this.pos += 4;
10971
+ return parseInt(hex, 16);
10972
+ }
10973
+ parseNumber() {
10974
+ const start = this.pos;
10975
+ if (this.text[this.pos] === "-") {
10976
+ this.pos++;
10977
+ }
10978
+ if (this.atEnd()) {
10979
+ throw new CanonicalJsonError("UNEXPECTED_END");
10980
+ }
10981
+ const first = this.text[this.pos];
10982
+ if (first < "0" || first > "9") {
10983
+ throw new CanonicalJsonError("INVALID_TOKEN");
10984
+ }
10985
+ if (first === "0") {
10986
+ this.pos++;
10987
+ } else {
10988
+ while (!this.atEnd() && this.text[this.pos] >= "0" && this.text[this.pos] <= "9") {
10989
+ this.pos++;
10990
+ }
10991
+ }
10992
+ if (!this.atEnd()) {
10993
+ const next = this.text[this.pos];
10994
+ if (next >= "0" && next <= "9") {
10995
+ throw new CanonicalJsonError("INVALID_TOKEN");
10996
+ }
10997
+ if (next === "." || next === "e" || next === "E") {
10998
+ throw new CanonicalJsonError("NON_INTEGER_NUMBER");
10999
+ }
11000
+ }
11001
+ const value = BigInt(this.text.slice(start, this.pos));
11002
+ if (value < INT64_MIN || value > INT64_MAX) {
11003
+ throw new CanonicalJsonError("INTEGER_OUT_OF_RANGE");
11004
+ }
11005
+ return value;
11006
+ }
11007
+ };
11008
+ function encodeCanonicalJson(value) {
11009
+ return new TextEncoder().encode(encodeToString(value));
11010
+ }
11011
+ function encodeToString(value) {
11012
+ if (value === null) {
11013
+ return "null";
11014
+ }
11015
+ if (typeof value === "boolean") {
11016
+ return value ? "true" : "false";
11017
+ }
11018
+ if (typeof value === "bigint") {
11019
+ return value.toString();
11020
+ }
11021
+ if (typeof value === "string") {
11022
+ return encodeString(value);
11023
+ }
11024
+ if (Array.isArray(value)) {
11025
+ return `[${value.map(encodeToString).join(",")}]`;
11026
+ }
11027
+ const keys = [...value.keys()].sort(compareByCodePoints);
11028
+ const members = keys.map((key) => `${encodeString(key)}:${encodeToString(value.get(key))}`);
11029
+ return `{${members.join(",")}}`;
11030
+ }
11031
+ function compareByCodePoints(a, b) {
11032
+ let i = 0;
11033
+ let j = 0;
11034
+ while (i < a.length && j < b.length) {
11035
+ const ca = a.codePointAt(i);
11036
+ const cb = b.codePointAt(j);
11037
+ if (ca !== cb) {
11038
+ return ca - cb;
11039
+ }
11040
+ i += ca > 65535 ? 2 : 1;
11041
+ j += cb > 65535 ? 2 : 1;
11042
+ }
11043
+ return a.length - i - (b.length - j);
11044
+ }
11045
+ function encodeString(value) {
11046
+ let out = '"';
11047
+ for (const ch of value) {
11048
+ const code = ch.codePointAt(0);
11049
+ if (ch === '"') {
11050
+ out += '\\"';
11051
+ } else if (ch === "\\") {
11052
+ out += "\\\\";
11053
+ } else if (code === 8) {
11054
+ out += "\\b";
11055
+ } else if (code === 12) {
11056
+ out += "\\f";
11057
+ } else if (code === 10) {
11058
+ out += "\\n";
11059
+ } else if (code === 13) {
11060
+ out += "\\r";
11061
+ } else if (code === 9) {
11062
+ out += "\\t";
11063
+ } else if (code < 32) {
11064
+ out += `\\u00${code.toString(16).padStart(2, "0")}`;
11065
+ } else {
11066
+ out += ch;
11067
+ }
11068
+ }
11069
+ return out + '"';
11070
+ }
11071
+
11072
+ // src/server/client-proof/proof.ts
11073
+ import { createHash as createHash4, createPrivateKey, createPublicKey, sign, verify as verify2 } from "crypto";
11074
+ var CLIENT_PROOF_PROFILE = "clientProofV1";
11075
+ var ABSENT_BODY_SHA256 = "0".repeat(64);
11076
+ var DEFAULT_REPLAY_WINDOW_MILLIS = 3e5;
11077
+ var PROOF_INPUT_FIELDS = [
11078
+ "profile",
11079
+ "method",
11080
+ "path",
11081
+ "clientId",
11082
+ "keyId",
11083
+ "nonce",
11084
+ "issuedAtMillis",
11085
+ "bodySha256"
11086
+ ];
11087
+ var PROOF_INPUT_SEPARATOR = "\n";
11088
+ var PROOF_SIGNATURE_BYTES = 64;
11089
+ var PROOF_SIGNATURE_HEX_LENGTH = PROOF_SIGNATURE_BYTES * 2;
11090
+ var PROOF_SIGNATURE_PATTERN = /^[0-9a-f]{128}$/;
11091
+ var RAW_SIGNATURE_ENCODING = "ieee-p1363";
11092
+ var ProofInputError = class extends Error {
11093
+ constructor() {
11094
+ super("proof input field contains a C0 control character");
11095
+ this.name = "ProofInputError";
11096
+ }
11097
+ };
11098
+ function canonicalProofInput(input) {
11099
+ const values = {
11100
+ profile: CLIENT_PROOF_PROFILE,
11101
+ method: input.method,
11102
+ path: input.path,
11103
+ clientId: input.clientId,
11104
+ keyId: input.keyId,
11105
+ nonce: input.nonce,
11106
+ issuedAtMillis: input.issuedAtMillis.toString(),
11107
+ bodySha256: input.bodySha256
11108
+ };
11109
+ const fields = PROOF_INPUT_FIELDS.map((name) => values[name]);
11110
+ for (const field of fields) {
11111
+ for (const ch of field) {
11112
+ if (ch.codePointAt(0) < 32) {
11113
+ throw new ProofInputError();
11114
+ }
11115
+ }
11116
+ }
11117
+ return fields.join(PROOF_INPUT_SEPARATOR);
11118
+ }
11119
+ function parseClientProofPublicKey(spkiDerBase64) {
11120
+ const key = createPublicKey({
11121
+ key: Buffer.from(spkiDerBase64, "base64"),
11122
+ format: "der",
11123
+ type: "spki"
11124
+ });
11125
+ if (key.asymmetricKeyType !== "ec" || key.asymmetricKeyDetails?.namedCurve !== "prime256v1") {
11126
+ throw new Error("a clientProofV1 public key must be an ECDSA P-256 key");
11127
+ }
11128
+ return key;
11129
+ }
11130
+ function verifyClientProof(input, presentedProof, publicKey) {
11131
+ const data = Buffer.from(canonicalProofInput(input), "utf8");
11132
+ if (!PROOF_SIGNATURE_PATTERN.test(presentedProof)) {
11133
+ return false;
11134
+ }
11135
+ return verify2(
11136
+ "sha256",
11137
+ data,
11138
+ { key: publicKey, dsaEncoding: RAW_SIGNATURE_ENCODING },
11139
+ Buffer.from(presentedProof, "hex")
11140
+ );
11141
+ }
11142
+ function sha256Hex(bytes) {
11143
+ return createHash4("sha256").update(bytes).digest("hex");
11144
+ }
11145
+
11146
+ // src/server/client-proof/refusal.ts
11147
+ import { randomBytes } from "crypto";
11148
+ var HTTP_STATUS = {
11149
+ PROOF_INVALID: 401,
11150
+ PROOF_REPLAYED: 401,
11151
+ PROOF_EXPIRED: 401,
11152
+ SESSION_REVOKED: 401,
11153
+ PROFILE_REJECTED: 400,
11154
+ CONTRACT_UNSUPPORTED: 409
11155
+ };
11156
+ var ClientProofRefusal = class _ClientProofRefusal {
11157
+ constructor(code, message) {
11158
+ this.code = code;
11159
+ this.message = message;
11160
+ }
11161
+ get httpStatus() {
11162
+ return HTTP_STATUS[this.code];
11163
+ }
11164
+ /** The canonical bytes of `{"error":{"code":…,"message":…,"requestId":…}}`. */
11165
+ envelopeBytes(requestId) {
11166
+ const error = /* @__PURE__ */ new Map([
11167
+ ["code", this.code],
11168
+ ["message", this.message],
11169
+ ["requestId", requestId]
11170
+ ]);
11171
+ return encodeCanonicalJson(/* @__PURE__ */ new Map([["error", error]]));
11172
+ }
11173
+ /** Nothing request-derived reaches a log through this. */
11174
+ toString() {
11175
+ return `ClientProofRefusal(${this.code})`;
11176
+ }
11177
+ // ---- shape: what arrived is not the contract (rule 2) -------------------
11178
+ static unroutable() {
11179
+ return contractViolation("no operation in this contract answers that method and path");
11180
+ }
11181
+ static malformedHeaders() {
11182
+ return contractViolation("the request does not carry the contract header fields exactly once each");
11183
+ }
11184
+ static missingContentType() {
11185
+ return contractViolation("a request that carries a body must declare the contract content type");
11186
+ }
11187
+ static bodyTooLarge() {
11188
+ return contractViolation("the request body exceeds the size this server accepts");
11189
+ }
11190
+ /**
11191
+ * The body parsed but its bytes are not the canonical form of what it
11192
+ * parsed to. Not PROOF_INVALID even though it is discovered next to the
11193
+ * proof: the proof over these bytes verifies perfectly well, and an
11194
+ * auth-family answer would tell the client to re-handshake and send the
11195
+ * same non-canonical bytes again.
11196
+ */
11197
+ static bodyNotCanonical() {
11198
+ return contractViolation("the request body is not the canonical JSON form of the value it encodes");
11199
+ }
11200
+ static bodyNotTheDeclaredType() {
11201
+ return contractViolation("the request body is not the request type this operation declares");
11202
+ }
11203
+ static sessionHeaderMisplaced() {
11204
+ return contractViolation("the session header is present exactly on the operations that require one");
11205
+ }
11206
+ static unprocessable() {
11207
+ return contractViolation("the request could not be processed");
11208
+ }
11209
+ // ---- the profile allowlist ----------------------------------------------
11210
+ static profileRejected() {
11211
+ return new _ClientProofRefusal("PROFILE_REJECTED", "the named auth profile is not on this contract's allowlist");
11212
+ }
11213
+ // ---- auth: a new session might clear it (rule 1) -------------------------
11214
+ static sessionRevoked() {
11215
+ return new _ClientProofRefusal("SESSION_REVOKED", "the key or session was revoked");
11216
+ }
11217
+ static proofExpired() {
11218
+ return new _ClientProofRefusal("PROOF_EXPIRED", "issuedAtMillis falls outside the replay window");
11219
+ }
11220
+ static proofReplayed() {
11221
+ return new _ClientProofRefusal("PROOF_REPLAYED", "the nonce was already used inside the replay window");
11222
+ }
11223
+ static proofInvalid() {
11224
+ return new _ClientProofRefusal("PROOF_INVALID", "the client proof did not verify");
11225
+ }
11226
+ };
11227
+ function contractViolation(message) {
11228
+ return new ClientProofRefusal("CONTRACT_UNSUPPORTED", message);
11229
+ }
11230
+
11231
+ // src/server/client-proof/admission.ts
11232
+ var CLIENT_PROOF_HEADERS = {
11233
+ profile: "x-spfn-auth-profile",
11234
+ clientId: "x-spfn-client-id",
11235
+ keyId: "x-spfn-key-id",
11236
+ nonce: "x-spfn-nonce",
11237
+ issuedAtMillis: "x-spfn-issued-at",
11238
+ proof: "x-spfn-proof",
11239
+ session: "x-spfn-session"
11240
+ };
11241
+ var CLIENT_PROOF_CONTENT_TYPE = "application/json";
11242
+ var INT64_MIN2 = -(2n ** 63n);
11243
+ var INT64_MAX2 = 2n ** 63n - 1n;
11244
+ function readCredentials(headers) {
11245
+ const profile = headers.get(CLIENT_PROOF_HEADERS.profile);
11246
+ const clientId = headers.get(CLIENT_PROOF_HEADERS.clientId);
11247
+ const keyId = headers.get(CLIENT_PROOF_HEADERS.keyId);
11248
+ const nonce = headers.get(CLIENT_PROOF_HEADERS.nonce);
11249
+ const issuedAtRaw = headers.get(CLIENT_PROOF_HEADERS.issuedAtMillis);
11250
+ const proof = headers.get(CLIENT_PROOF_HEADERS.proof);
11251
+ if (profile === null || clientId === null || keyId === null || nonce === null || issuedAtRaw === null || proof === null) {
11252
+ return null;
11253
+ }
11254
+ const issuedAtMillis = parseInt64(issuedAtRaw);
11255
+ if (issuedAtMillis === null) {
11256
+ return null;
11257
+ }
11258
+ return {
11259
+ profile,
11260
+ clientId,
11261
+ keyId,
11262
+ nonce,
11263
+ issuedAtMillis,
11264
+ proof,
11265
+ sessionId: headers.get(CLIENT_PROOF_HEADERS.session)
11266
+ };
11267
+ }
11268
+ function parseInt64(raw) {
11269
+ if (!/^[+-]?\d{1,19}$/.test(raw)) {
11270
+ return null;
11271
+ }
11272
+ const value = BigInt(raw);
11273
+ if (value < INT64_MIN2 || value > INT64_MAX2) {
11274
+ return null;
11275
+ }
11276
+ return value;
11277
+ }
11278
+ function isRequestContentType(value) {
11279
+ if (value === null) {
11280
+ return false;
11281
+ }
11282
+ return value.split(";")[0].trim().toLowerCase() === CLIENT_PROOF_CONTENT_TYPE;
11283
+ }
11284
+
11285
+ // src/server/client-proof/replay-store.ts
11286
+ import { getCache } from "@spfn/core/cache";
11287
+ function replayLedgerKey(clientId, nonce) {
11288
+ return JSON.stringify([clientId, nonce]);
11289
+ }
11290
+ var MemoryReplayLedger = class {
11291
+ /** replayLedgerKey(...) → the millis it was spent at. */
11292
+ spent = /* @__PURE__ */ new Map();
11293
+ isSpent(clientId, nonce) {
11294
+ return this.spent.has(replayLedgerKey(clientId, nonce));
11295
+ }
11296
+ /** Records the pair at `atMillis`; false when it was already spent. */
11297
+ spend(clientId, nonce, atMillis) {
11298
+ const key = replayLedgerKey(clientId, nonce);
11299
+ if (this.spent.has(key)) {
11300
+ return false;
11301
+ }
11302
+ this.spent.set(key, atMillis);
11303
+ return true;
11304
+ }
11305
+ /** Drops entries older than the window, judged against `nowMillis`. */
11306
+ prune(nowMillis, windowMillis) {
11307
+ for (const [key, spentAtMillis] of this.spent) {
11308
+ if (nowMillis - spentAtMillis > windowMillis) {
11309
+ this.spent.delete(key);
11310
+ }
11311
+ }
11312
+ }
11313
+ get size() {
11314
+ return this.spent.size;
11315
+ }
11316
+ clear() {
11317
+ this.spent.clear();
11318
+ }
11319
+ };
11320
+ var MemoryReplayStore = class {
11321
+ constructor(windowMillis = DEFAULT_REPLAY_WINDOW_MILLIS) {
11322
+ this.windowMillis = windowMillis;
11323
+ }
11324
+ ledger = new MemoryReplayLedger();
11325
+ async isSpent(clientId, nonce) {
11326
+ this.ledger.prune(Date.now(), this.windowMillis);
11327
+ return this.ledger.isSpent(clientId, nonce);
11328
+ }
11329
+ async spend(clientId, nonce) {
11330
+ const now = Date.now();
11331
+ this.ledger.prune(now, this.windowMillis);
11332
+ return this.ledger.spend(clientId, nonce, now);
11333
+ }
11334
+ };
11335
+ var configured = null;
11336
+ function getClientProofReplayStore() {
11337
+ configured ??= new MemoryReplayStore();
11338
+ return configured;
11339
+ }
11340
+
11341
+ // src/server/middleware/auth-profiles.ts
11342
+ function selectAuthProfile(c) {
11343
+ const profile = c.req.header(CLIENT_PROOF_HEADERS.profile);
11344
+ if (profile === void 0) {
11345
+ return null;
11346
+ }
11347
+ if (c.req.header("Authorization") !== void 0) {
11348
+ throw new BadRequestError2({
11349
+ message: "an auth profile and Bearer credentials must not be mixed in one request",
11350
+ details: { code: "PROFILE_REJECTED" }
11351
+ });
11352
+ }
11353
+ const verifier = AUTH_PROFILE_VERIFIERS.get(profile);
11354
+ if (verifier === void 0) {
11355
+ throw refusalError(ClientProofRefusal.profileRejected());
11356
+ }
11357
+ return verifier;
11358
+ }
11359
+ async function resolveAuthenticatedUser(userId) {
11360
+ const [result, locale] = await Promise.all([
11361
+ usersRepository2.findByIdWithRole(userId),
11362
+ userProfilesRepository2.findLocaleByUserId(userId)
11363
+ ]);
11364
+ if (!result) {
11365
+ throw new UnauthorizedError({ message: "User not found" });
11366
+ }
11367
+ const { user, role } = result;
11368
+ if (user.status !== "active") {
11369
+ if (user.status === "pending_deletion") {
11370
+ const pending = await getPendingDeletionInfo2(user.id);
11371
+ throw new AccountPendingDeletionError3({ purgeScheduledAt: pending?.purgeScheduledAt.toISOString() });
11372
+ }
11373
+ throw new AccountDisabledError3({ status: user.status });
11374
+ }
11375
+ return { user, role: role?.name ?? null, locale };
11376
+ }
11377
+ function refusalError(refusal) {
11378
+ const data = { message: refusal.message, details: { code: refusal.code } };
11379
+ if (refusal.code === "PROFILE_REJECTED") {
11380
+ return new BadRequestError2(data);
11381
+ }
11382
+ if (refusal.code === "CONTRACT_UNSUPPORTED") {
11383
+ return new ConflictError2(data);
11384
+ }
11385
+ return new UnauthorizedError(data);
11386
+ }
11387
+ async function failClosed(operation) {
11388
+ try {
11389
+ return await operation();
11390
+ } catch (err) {
11391
+ authLogger2.middleware.error("client-proof replay store unavailable \u2014 refusing (fail-closed)", err);
11392
+ throw new ServiceUnavailableError({ message: "Authentication is temporarily unavailable" });
11393
+ }
11394
+ }
11395
+ async function verifyClientProofProfile(c) {
11396
+ const headers = c.req.raw.headers;
11397
+ const credentials = readCredentials(headers);
11398
+ if (credentials === null) {
11399
+ throw refusalError(ClientProofRefusal.malformedHeaders());
11400
+ }
11401
+ if (credentials.profile !== CLIENT_PROOF_PROFILE) {
11402
+ throw refusalError(ClientProofRefusal.profileRejected());
11403
+ }
11404
+ if (credentials.sessionId !== null) {
11405
+ throw refusalError(ClientProofRefusal.sessionHeaderMisplaced());
11406
+ }
11407
+ const body = new Uint8Array(await c.req.arrayBuffer());
11408
+ const proofInput = {
11409
+ method: c.req.method,
11410
+ path: c.req.path,
11411
+ clientId: credentials.clientId,
11412
+ keyId: credentials.keyId,
11413
+ nonce: credentials.nonce,
11414
+ issuedAtMillis: credentials.issuedAtMillis,
11415
+ bodySha256: admitBodySha256(headers, body)
11416
+ };
11417
+ const keyRecord = await keysRepository2.findByKeyId(credentials.keyId);
11418
+ if (keyRecord !== null) {
11419
+ if (!keyRecord.isActive) {
11420
+ throw refusalError(ClientProofRefusal.sessionRevoked());
11421
+ }
11422
+ if (keyRecord.expiresAt && /* @__PURE__ */ new Date() > keyRecord.expiresAt) {
11423
+ throw refusalError(ClientProofRefusal.sessionRevoked());
11424
+ }
11425
+ }
11426
+ const age = Date.now() - Number(credentials.issuedAtMillis);
11427
+ if (age < 0 || age > DEFAULT_REPLAY_WINDOW_MILLIS) {
11428
+ throw refusalError(ClientProofRefusal.proofExpired());
11429
+ }
11430
+ const replayStore = getClientProofReplayStore();
11431
+ if (await failClosed(() => replayStore.isSpent(credentials.clientId, credentials.nonce))) {
11432
+ throw refusalError(ClientProofRefusal.proofReplayed());
11433
+ }
11434
+ if (keyRecord === null) {
11435
+ throw refusalError(ClientProofRefusal.proofInvalid());
11436
+ }
11437
+ if (credentials.clientId !== String(keyRecord.userId)) {
11438
+ throw refusalError(ClientProofRefusal.proofInvalid());
11439
+ }
11440
+ verifyProofOrThrow(proofInput, credentials, keyRecord.publicKey);
11441
+ if (!await failClosed(() => replayStore.spend(credentials.clientId, credentials.nonce))) {
11442
+ throw refusalError(ClientProofRefusal.proofReplayed());
11443
+ }
11444
+ const { user, role, locale } = await resolveAuthenticatedUser(keyRecord.userId);
11445
+ keysRepository2.updateLastUsedById(keyRecord.id).catch((err) => authLogger2.middleware.error("Failed to update lastUsedAt", err));
11446
+ authLogger2.middleware.info("API access", {
11447
+ userId: user.id,
11448
+ email: user.email,
11449
+ keyId: credentials.keyId,
11450
+ scheme: CLIENT_PROOF_PROFILE,
11451
+ method: c.req.method,
11452
+ path: c.req.path,
11453
+ ip: c.req.header("x-forwarded-for") || c.req.header("x-real-ip"),
11454
+ userAgent: c.req.header("user-agent")
11455
+ });
11456
+ return {
11457
+ user,
11458
+ userId: String(user.id),
11459
+ keyId: credentials.keyId,
11460
+ role,
11461
+ locale,
11462
+ scheme: "clientProofV1"
11463
+ };
11464
+ }
11465
+ function admitBodySha256(headers, body) {
11466
+ if (body.length === 0) {
11467
+ return ABSENT_BODY_SHA256;
11468
+ }
11469
+ if (!isRequestContentType(headers.get("content-type"))) {
11470
+ throw refusalError(ClientProofRefusal.missingContentType());
11471
+ }
11472
+ let value;
11473
+ try {
11474
+ value = parseCanonicalJson(body);
11475
+ } catch {
11476
+ throw refusalError(ClientProofRefusal.bodyNotCanonical());
11477
+ }
11478
+ if (!isCanonicalBytes(body, value)) {
11479
+ throw refusalError(ClientProofRefusal.bodyNotCanonical());
11480
+ }
11481
+ return sha256Hex(body);
11482
+ }
11483
+ function verifyProofOrThrow(proofInput, credentials, publicKeySpkiDerBase64) {
11484
+ let verified;
11485
+ try {
11486
+ const publicKey = parseClientProofPublicKey(publicKeySpkiDerBase64);
11487
+ verified = verifyClientProof(proofInput, credentials.proof, publicKey);
11488
+ } catch (err) {
11489
+ if (err instanceof ProofInputError) {
11490
+ throw refusalError(ClientProofRefusal.unprocessable());
11491
+ }
11492
+ throw refusalError(ClientProofRefusal.proofInvalid());
11493
+ }
11494
+ if (!verified) {
11495
+ throw refusalError(ClientProofRefusal.proofInvalid());
11496
+ }
11497
+ }
11498
+ var AUTH_PROFILE_VERIFIERS = /* @__PURE__ */ new Map([
11499
+ [CLIENT_PROOF_PROFILE, { verify: verifyClientProofProfile }]
11500
+ ]);
11501
+
11502
+ // src/server/middleware/authenticate.ts
10697
11503
  var authenticate = defineMiddleware("auth", async (c, next) => {
11504
+ const profileVerifier = selectAuthProfile(c);
11505
+ if (profileVerifier !== null) {
11506
+ c.set("auth", await profileVerifier.verify(c));
11507
+ await next();
11508
+ return;
11509
+ }
10698
11510
  const authHeader = c.req.header("Authorization");
10699
11511
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
10700
- authLogger2.middleware.error("Missing or invalid authorization header. If using Next.js API routes, ensure you have imported '@spfn/auth/nextjs/api' in your API route handler (e.g., src/app/api/actions/[[...path]]/route.ts) to enable automatic authentication header forwarding from client to backend.", {
11512
+ authLogger3.middleware.error("Missing or invalid authorization header. If using Next.js API routes, ensure you have imported '@spfn/auth/nextjs/api' in your API route handler (e.g., src/app/api/actions/[[...path]]/route.ts) to enable automatic authentication header forwarding from client to backend.", {
10701
11513
  headers: c.req.header(),
10702
11514
  path: c.req.path
10703
11515
  });
10704
- throw new UnauthorizedError({ message: "Authentication header missing or invalid: Bearer {token}" });
11516
+ throw new UnauthorizedError2({ message: "Authentication header missing or invalid: Bearer {token}" });
10705
11517
  }
10706
11518
  const token = authHeader.substring(7);
10707
11519
  const decoded = decodeToken2(token);
10708
11520
  if (!decoded || !decoded.keyId) {
10709
- throw new UnauthorizedError({ message: "Invalid token: missing keyId" });
11521
+ throw new UnauthorizedError2({ message: "Invalid token: missing keyId" });
10710
11522
  }
10711
11523
  const keyId = decoded.keyId;
10712
- const keyRecord = await keysRepository2.findActiveByKeyId(keyId);
11524
+ const keyRecord = await keysRepository3.findActiveByKeyId(keyId);
10713
11525
  if (!keyRecord) {
10714
- throw new UnauthorizedError({ message: "Invalid or revoked key" });
11526
+ throw new UnauthorizedError2({ message: "Invalid or revoked key" });
10715
11527
  }
10716
11528
  if (keyRecord.expiresAt && /* @__PURE__ */ new Date() > keyRecord.expiresAt) {
10717
11529
  throw new KeyExpiredError();
@@ -10732,34 +11544,21 @@ var authenticate = defineMiddleware("auth", async (c, next) => {
10732
11544
  throw new InvalidTokenError({ message: "Invalid token signature" });
10733
11545
  }
10734
11546
  }
10735
- throw new UnauthorizedError({ message: "Authentication failed" });
10736
- }
10737
- const [result, locale] = await Promise.all([
10738
- usersRepository2.findByIdWithRole(keyRecord.userId),
10739
- userProfilesRepository2.findLocaleByUserId(keyRecord.userId)
10740
- ]);
10741
- if (!result) {
10742
- throw new UnauthorizedError({ message: "User not found" });
10743
- }
10744
- const { user, role } = result;
10745
- if (user.status !== "active") {
10746
- if (user.status === "pending_deletion") {
10747
- const pending = await getPendingDeletionInfo2(user.id);
10748
- throw new AccountPendingDeletionError3({ purgeScheduledAt: pending?.purgeScheduledAt.toISOString() });
10749
- }
10750
- throw new AccountDisabledError3({ status: user.status });
11547
+ throw new UnauthorizedError2({ message: "Authentication failed" });
10751
11548
  }
10752
- keysRepository2.updateLastUsedById(keyRecord.id).catch((err) => authLogger2.middleware.error("Failed to update lastUsedAt", err));
11549
+ const { user, role, locale } = await resolveAuthenticatedUser(keyRecord.userId);
11550
+ keysRepository3.updateLastUsedById(keyRecord.id).catch((err) => authLogger3.middleware.error("Failed to update lastUsedAt", err));
10753
11551
  c.set("auth", {
10754
11552
  user,
10755
11553
  userId: String(user.id),
10756
11554
  keyId,
10757
- role: role?.name ?? null,
10758
- locale
11555
+ role,
11556
+ locale,
11557
+ scheme: "bearer"
10759
11558
  });
10760
11559
  const method = c.req.method;
10761
11560
  const path = c.req.path;
10762
- authLogger2.middleware.info("API access", {
11561
+ authLogger3.middleware.info("API access", {
10763
11562
  userId: user.id,
10764
11563
  email: user.email,
10765
11564
  keyId,
@@ -10771,6 +11570,12 @@ var authenticate = defineMiddleware("auth", async (c, next) => {
10771
11570
  await next();
10772
11571
  });
10773
11572
  var optionalAuth = defineMiddleware("optionalAuth", async (c, next) => {
11573
+ const profileVerifier = selectAuthProfile(c);
11574
+ if (profileVerifier !== null) {
11575
+ c.set("auth", await profileVerifier.verify(c));
11576
+ await next();
11577
+ return;
11578
+ }
10774
11579
  const authHeader = c.req.header("Authorization");
10775
11580
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
10776
11581
  await next();
@@ -10784,7 +11589,7 @@ var optionalAuth = defineMiddleware("optionalAuth", async (c, next) => {
10784
11589
  return;
10785
11590
  }
10786
11591
  const keyId = decoded.keyId;
10787
- const keyRecord = await keysRepository2.findActiveByKeyId(keyId);
11592
+ const keyRecord = await keysRepository3.findActiveByKeyId(keyId);
10788
11593
  if (!keyRecord) {
10789
11594
  await next();
10790
11595
  return;
@@ -10799,21 +11604,22 @@ var optionalAuth = defineMiddleware("optionalAuth", async (c, next) => {
10799
11604
  keyRecord.algorithm
10800
11605
  );
10801
11606
  const [result, locale] = await Promise.all([
10802
- usersRepository2.findByIdWithRole(keyRecord.userId),
10803
- userProfilesRepository2.findLocaleByUserId(keyRecord.userId)
11607
+ usersRepository3.findByIdWithRole(keyRecord.userId),
11608
+ userProfilesRepository3.findLocaleByUserId(keyRecord.userId)
10804
11609
  ]);
10805
11610
  if (!result || result.user.status !== "active") {
10806
11611
  await next();
10807
11612
  return;
10808
11613
  }
10809
11614
  const { user, role } = result;
10810
- keysRepository2.updateLastUsedById(keyRecord.id).catch((err) => authLogger2.middleware.error("Failed to update lastUsedAt", err));
11615
+ keysRepository3.updateLastUsedById(keyRecord.id).catch((err) => authLogger3.middleware.error("Failed to update lastUsedAt", err));
10811
11616
  c.set("auth", {
10812
11617
  user,
10813
11618
  userId: String(user.id),
10814
11619
  keyId,
10815
11620
  role: role?.name ?? null,
10816
- locale
11621
+ locale,
11622
+ scheme: "bearer"
10817
11623
  });
10818
11624
  } catch {
10819
11625
  }
@@ -10824,13 +11630,13 @@ var optionalAuth = defineMiddleware("optionalAuth", async (c, next) => {
10824
11630
  import { defineMiddleware as defineMiddleware2 } from "@spfn/core/route";
10825
11631
  import { ForbiddenError as ForbiddenError2 } from "@spfn/core/errors";
10826
11632
  import { InsufficientPermissionsError } from "@spfn/auth/errors";
10827
- import { getAuth as getAuth2, hasAllPermissions as hasAllPermissions2, hasAnyPermission as hasAnyPermission2, authLogger as authLogger3 } from "@spfn/auth/server";
11633
+ import { getAuth as getAuth2, hasAllPermissions as hasAllPermissions2, hasAnyPermission as hasAnyPermission2, authLogger as authLogger4 } from "@spfn/auth/server";
10828
11634
  var requirePermissions = defineMiddleware2(
10829
11635
  "permission",
10830
11636
  (...permissionNames) => async (c, next) => {
10831
11637
  const auth = getAuth2(c);
10832
11638
  if (!auth) {
10833
- authLogger3.middleware.warn("Permission check failed: not authenticated", {
11639
+ authLogger4.middleware.warn("Permission check failed: not authenticated", {
10834
11640
  permissions: permissionNames,
10835
11641
  path: c.req.path
10836
11642
  });
@@ -10839,14 +11645,14 @@ var requirePermissions = defineMiddleware2(
10839
11645
  const { userId } = auth;
10840
11646
  const allowed = await hasAllPermissions2(userId, permissionNames);
10841
11647
  if (!allowed) {
10842
- authLogger3.middleware.warn("Permission check failed", {
11648
+ authLogger4.middleware.warn("Permission check failed", {
10843
11649
  userId,
10844
11650
  requiredPermissions: permissionNames,
10845
11651
  path: c.req.path
10846
11652
  });
10847
11653
  throw new InsufficientPermissionsError({ requiredPermissions: permissionNames });
10848
11654
  }
10849
- authLogger3.middleware.debug("Permission check passed", {
11655
+ authLogger4.middleware.debug("Permission check passed", {
10850
11656
  userId,
10851
11657
  permissions: permissionNames
10852
11658
  });
@@ -10858,7 +11664,7 @@ var requireAnyPermission = defineMiddleware2(
10858
11664
  (...permissionNames) => async (c, next) => {
10859
11665
  const auth = getAuth2(c);
10860
11666
  if (!auth) {
10861
- authLogger3.middleware.warn("Any permission check failed: not authenticated", {
11667
+ authLogger4.middleware.warn("Any permission check failed: not authenticated", {
10862
11668
  permissions: permissionNames,
10863
11669
  path: c.req.path
10864
11670
  });
@@ -10867,14 +11673,14 @@ var requireAnyPermission = defineMiddleware2(
10867
11673
  const { userId } = auth;
10868
11674
  const allowed = await hasAnyPermission2(userId, permissionNames);
10869
11675
  if (!allowed) {
10870
- authLogger3.middleware.warn("Any permission check failed", {
11676
+ authLogger4.middleware.warn("Any permission check failed", {
10871
11677
  userId,
10872
11678
  requiredAnyOf: permissionNames,
10873
11679
  path: c.req.path
10874
11680
  });
10875
11681
  throw new InsufficientPermissionsError({ requiredPermissions: permissionNames });
10876
11682
  }
10877
- authLogger3.middleware.debug("Any permission check passed", {
11683
+ authLogger4.middleware.debug("Any permission check passed", {
10878
11684
  userId,
10879
11685
  permissions: permissionNames
10880
11686
  });
@@ -10884,7 +11690,7 @@ var requireAnyPermission = defineMiddleware2(
10884
11690
 
10885
11691
  // src/server/middleware/require-role.ts
10886
11692
  import { defineMiddleware as defineMiddleware3 } from "@spfn/core/route";
10887
- import { getAuth as getAuth3, authLogger as authLogger4 } from "@spfn/auth/server";
11693
+ import { getAuth as getAuth3, authLogger as authLogger5 } from "@spfn/auth/server";
10888
11694
  import { ForbiddenError as ForbiddenError3 } from "@spfn/core/errors";
10889
11695
  import { InsufficientRoleError } from "@spfn/auth/errors";
10890
11696
  var requireRole = defineMiddleware3(
@@ -10892,7 +11698,7 @@ var requireRole = defineMiddleware3(
10892
11698
  (...roleNames) => async (c, next) => {
10893
11699
  const auth = getAuth3(c);
10894
11700
  if (!auth) {
10895
- authLogger4.middleware.warn("Role check failed: not authenticated", {
11701
+ authLogger5.middleware.warn("Role check failed: not authenticated", {
10896
11702
  roles: roleNames,
10897
11703
  path: c.req.path
10898
11704
  });
@@ -10900,7 +11706,7 @@ var requireRole = defineMiddleware3(
10900
11706
  }
10901
11707
  const { userId, role: userRole } = auth;
10902
11708
  if (!userRole || !roleNames.includes(userRole)) {
10903
- authLogger4.middleware.warn("Role check failed", {
11709
+ authLogger5.middleware.warn("Role check failed", {
10904
11710
  userId,
10905
11711
  userRole,
10906
11712
  requiredRoles: roleNames,
@@ -10908,7 +11714,7 @@ var requireRole = defineMiddleware3(
10908
11714
  });
10909
11715
  throw new InsufficientRoleError({ requiredRoles: roleNames });
10910
11716
  }
10911
- authLogger4.middleware.debug("Role check passed", {
11717
+ authLogger5.middleware.debug("Role check passed", {
10912
11718
  userId,
10913
11719
  userRole,
10914
11720
  roles: roleNames
@@ -10919,7 +11725,7 @@ var requireRole = defineMiddleware3(
10919
11725
 
10920
11726
  // src/server/middleware/role-guard.ts
10921
11727
  import { defineMiddleware as defineMiddleware4 } from "@spfn/core/route";
10922
- import { getAuth as getAuth4, authLogger as authLogger5 } from "@spfn/auth/server";
11728
+ import { getAuth as getAuth4, authLogger as authLogger6 } from "@spfn/auth/server";
10923
11729
  import { ForbiddenError as ForbiddenError4 } from "@spfn/core/errors";
10924
11730
  import { InsufficientRoleError as InsufficientRoleError2 } from "@spfn/auth/errors";
10925
11731
  var roleGuard = defineMiddleware4(
@@ -10931,7 +11737,7 @@ var roleGuard = defineMiddleware4(
10931
11737
  }
10932
11738
  const auth = getAuth4(c);
10933
11739
  if (!auth) {
10934
- authLogger5.middleware.warn("Role guard failed: not authenticated", {
11740
+ authLogger6.middleware.warn("Role guard failed: not authenticated", {
10935
11741
  path: c.req.path
10936
11742
  });
10937
11743
  throw new ForbiddenError4({ message: "Authentication required" });
@@ -10939,7 +11745,7 @@ var roleGuard = defineMiddleware4(
10939
11745
  const { userId, role: userRole } = auth;
10940
11746
  if (deny && deny.length > 0) {
10941
11747
  if (userRole && deny.includes(userRole)) {
10942
- authLogger5.middleware.warn("Role guard denied", {
11748
+ authLogger6.middleware.warn("Role guard denied", {
10943
11749
  userId,
10944
11750
  userRole,
10945
11751
  deniedRoles: deny,
@@ -10950,7 +11756,7 @@ var roleGuard = defineMiddleware4(
10950
11756
  }
10951
11757
  if (allow && allow.length > 0) {
10952
11758
  if (!userRole || !allow.includes(userRole)) {
10953
- authLogger5.middleware.warn("Role guard failed: role not allowed", {
11759
+ authLogger6.middleware.warn("Role guard failed: role not allowed", {
10954
11760
  userId,
10955
11761
  userRole,
10956
11762
  allowedRoles: allow,
@@ -10959,7 +11765,7 @@ var roleGuard = defineMiddleware4(
10959
11765
  throw new InsufficientRoleError2({ requiredRoles: allow });
10960
11766
  }
10961
11767
  }
10962
- authLogger5.middleware.debug("Role guard passed", {
11768
+ authLogger6.middleware.debug("Role guard passed", {
10963
11769
  userId,
10964
11770
  userRole,
10965
11771
  allow,
@@ -10971,27 +11777,27 @@ var roleGuard = defineMiddleware4(
10971
11777
 
10972
11778
  // src/server/middleware/one-time-token-auth.ts
10973
11779
  import { defineMiddleware as defineMiddleware5 } from "@spfn/core/route";
10974
- import { UnauthorizedError as UnauthorizedError2 } from "@spfn/core/errors";
10975
- import { usersRepository as usersRepository3, userProfilesRepository as userProfilesRepository3 } from "@spfn/auth/server";
11780
+ import { UnauthorizedError as UnauthorizedError3 } from "@spfn/core/errors";
11781
+ import { usersRepository as usersRepository4, userProfilesRepository as userProfilesRepository4 } from "@spfn/auth/server";
10976
11782
  var oneTimeTokenAuth = defineMiddleware5("oneTimeTokenAuth", async (c, next) => {
10977
11783
  const token = c.req.query("token") ?? extractOTTHeader(c.req.header("Authorization"));
10978
11784
  if (!token) {
10979
- throw new UnauthorizedError2({ message: "One-time token required: ?token=xxx or Authorization: OTT xxx" });
11785
+ throw new UnauthorizedError3({ message: "One-time token required: ?token=xxx or Authorization: OTT xxx" });
10980
11786
  }
10981
11787
  const userId = await verifyOneTimeTokenService(token);
10982
11788
  if (!userId) {
10983
- throw new UnauthorizedError2({ message: "Invalid or expired one-time token" });
11789
+ throw new UnauthorizedError3({ message: "Invalid or expired one-time token" });
10984
11790
  }
10985
11791
  const [result, locale] = await Promise.all([
10986
- usersRepository3.findByIdWithRole(Number(userId)),
10987
- userProfilesRepository3.findLocaleByUserId(Number(userId))
11792
+ usersRepository4.findByIdWithRole(Number(userId)),
11793
+ userProfilesRepository4.findLocaleByUserId(Number(userId))
10988
11794
  ]);
10989
11795
  if (!result) {
10990
- throw new UnauthorizedError2({ message: "User not found" });
11796
+ throw new UnauthorizedError3({ message: "User not found" });
10991
11797
  }
10992
11798
  const { user, role } = result;
10993
11799
  if (user.status !== "active") {
10994
- throw new UnauthorizedError2({ message: "Account is not active" });
11800
+ throw new UnauthorizedError3({ message: "Account is not active" });
10995
11801
  }
10996
11802
  c.set("auth", {
10997
11803
  user,
@@ -10999,7 +11805,8 @@ var oneTimeTokenAuth = defineMiddleware5("oneTimeTokenAuth", async (c, next) =>
10999
11805
  keyId: "",
11000
11806
  // No key involved in OTT auth
11001
11807
  role: role?.name ?? null,
11002
- locale
11808
+ locale,
11809
+ scheme: "oneTimeToken"
11003
11810
  });
11004
11811
  await next();
11005
11812
  }, { skips: ["auth"] });
@@ -12476,6 +13283,7 @@ export {
12476
13283
  requirePermissions,
12477
13284
  requireRole,
12478
13285
  resendInvitation,
13286
+ resolveAuthenticatedUser,
12479
13287
  revokeKeyService,
12480
13288
  roleGuard,
12481
13289
  rolePermissions,
@@ -12485,6 +13293,7 @@ export {
12485
13293
  rotateKeyService,
12486
13294
  runBeforeRegister,
12487
13295
  sealSession,
13296
+ selectAuthProfile,
12488
13297
  sendVerificationCodeService,
12489
13298
  setRolePermissions,
12490
13299
  shouldRefreshSession,