@aiquants/auth-core 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AI Quants / fehde-k
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # @aiquants/auth-core
2
+
3
+ Transport- and provider-agnostic **authentication core**: shared session/profile types, an email/domain **allowlist** policy, and localizable auth **error messages**. No framework or Google SDK dependency — pair it with a framework adapter (`@aiquants/auth-react-router`).
4
+
5
+ ## Install
6
+
7
+ Inside this monorepo it is already wired as a pnpm workspace package:
8
+
9
+ ```jsonc
10
+ "dependencies": { "@aiquants/auth-core": "workspace:*" }
11
+ ```
12
+
13
+ ## API
14
+
15
+ - `AuthenticationPayload`, `GoogleProfileBase`, `SessionProfile`, `MyGoogleProfile` — the session/profile types re-homed from the app's `@coji/remix-auth-google` module augmentation so non-auth code can import them without pulling in remix-auth.
16
+ - `emailDomainAllowlist(allowedEmails, allowedDomains)` → `(email) => boolean` — trim+lowercase normalized, logical OR of exact-email and domain match, fail-closed (throws when both lists empty). `parseAllowlistCsv(raw)` parses a comma-separated env value.
17
+ - `defaultAuthMessages` / `AuthMessages` / `resolveAuthMessages(over?)` — the Japanese login-page error strings, overridable per app.
18
+
19
+ ## Session profile shape (byte-compatible contract)
20
+
21
+ `SessionProfile` is the exact value persisted in the `"__session"` cookie under key `"user"`:
22
+
23
+ ```ts
24
+ { id, displayName, name: { familyName, givenName }, emails: [{ value }],
25
+ accessToken, refreshToken?, expirationDateMs?, provider, role? }
26
+ ```
27
+
28
+ `photo` / `_json` / `photos` are intentionally excluded. Changing this shape invalidates live 30-day session cookies.
29
+
30
+ MIT
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Email / domain allowlist policy (transport-agnostic).
3
+ * メール / ドメインの allowlist ポリシー (トランスポート非依存)。
4
+ *
5
+ * §8-1 (統一セマンティクス): 照合は trim + lowercase で正規化する。原本の TS 側 login gate は
6
+ * 大文字小文字を区別していたが、Python 側 (access_policy) の正規化に合わせて一元化する。
7
+ * これは許可を「緩める」方向の変更で、大文字混じりの許可メールが確実に通るようになる。
8
+ */
9
+ /**
10
+ * Builds an `isEmailAllowed(email)` predicate from allowed emails and domains.
11
+ * 許可メール / 許可ドメインから `isEmailAllowed(email)` 述語を生成する。
12
+ *
13
+ * @throws when both lists are empty after normalization (fail-closed configuration guard).
14
+ * 正規化後に両リストが空なら throw (fail-closed の設定ガード)。
15
+ */
16
+ declare const emailDomainAllowlist: (allowedEmails?: readonly string[], allowedDomains?: readonly string[]) => ((email: string | null | undefined) => boolean);
17
+ /**
18
+ * Parses a comma-separated allowlist env value into a trimmed, non-empty string list.
19
+ * カンマ区切りの allowlist 環境変数値を、トリム済みかつ空要素を除いた文字列配列へ変換する。
20
+ */
21
+ declare const parseAllowlistCsv: (raw: string | null | undefined) => string[];
22
+
23
+ /**
24
+ * Localizable auth error messages shown on the login page (`/auth/login?error=...`).
25
+ * ログインページに表示する認証エラーメッセージ (上書き可能)。
26
+ *
27
+ * 既定は原本 (auth.server.ts の GoogleStrategy verify コールバック) の日本語文言と一致させる。
28
+ */
29
+ type AuthMessages = {
30
+ /** メール未確認 (email_verified === false)。 */
31
+ emailNotVerified: string;
32
+ /** Google 側での認証失敗 (トークン/プロフィール欠落)。 */
33
+ googleFailed: string;
34
+ /** バックエンド検証が false を返した。 */
35
+ backendFailed: string;
36
+ /** allowlist で許可されていないユーザー。 */
37
+ notAllowed: string;
38
+ /** バックエンドサービスへ到達できない (getaddrinfo ENOTFOUND 等)。 */
39
+ backendUnreachable: string;
40
+ /** その他の予期せぬエラー。 */
41
+ unexpected: string;
42
+ };
43
+ /** パッケージ既定の日本語メッセージ (原本と一致)。 */
44
+ declare const defaultAuthMessages: AuthMessages;
45
+ /**
46
+ * Shallow-merges a partial override over the default messages.
47
+ * 部分上書きを既定メッセージへ浅くマージする。
48
+ */
49
+ declare const resolveAuthMessages: (over?: Partial<AuthMessages>) => AuthMessages;
50
+
51
+ /**
52
+ * Transport-agnostic auth profile & session types (re-homed from the app's
53
+ * `@coji/remix-auth-google` module augmentation so non-auth code can import them
54
+ * without pulling in remix-auth or the Google SDK).
55
+ *
56
+ * `@coji/remix-auth-google` の module augmentation で生やしていた認証プロフィール型を、
57
+ * remix-auth / Google SDK 非依存で使えるよう本パッケージへ再ホームした定義。
58
+ */
59
+ /**
60
+ * Authentication token payload stored alongside the profile in the session.
61
+ * セッションにプロフィールと共に保持する認証トークンペイロード。
62
+ */
63
+ type AuthenticationPayload = {
64
+ accessToken: string;
65
+ refreshToken?: string;
66
+ expirationDateMs?: number;
67
+ provider: string;
68
+ role?: string;
69
+ };
70
+ /**
71
+ * Structural subset of the Google profile that the session/auth flow relies on.
72
+ * セッション / 認証フローが依存する Google プロフィールの構造的サブセット。
73
+ */
74
+ type GoogleProfileBase = {
75
+ id: string;
76
+ displayName: string;
77
+ name: {
78
+ familyName: string;
79
+ givenName: string;
80
+ };
81
+ emails: {
82
+ value: string;
83
+ }[];
84
+ };
85
+ /**
86
+ * The exact shape persisted in the session cookie under key "user".
87
+ * `photo` / `_json` / `photos` are intentionally excluded (byte-compatible with the
88
+ * live 30-day session cookie — see the extraction plan §1.3).
89
+ *
90
+ * セッション cookie にキー "user" で保存される正準形状。photo/_json/photos は意図的に除外
91
+ * (稼働中の 30 日 cookie と byte 互換)。
92
+ */
93
+ type SessionProfile = GoogleProfileBase & AuthenticationPayload;
94
+ /**
95
+ * The per-request user profile: session profile plus the resolved photo URL.
96
+ * リクエストごとのユーザープロフィール。セッションプロフィール + 解決済み写真 URL。
97
+ */
98
+ type MyGoogleProfile = GoogleProfileBase & {
99
+ photo: string;
100
+ } & AuthenticationPayload;
101
+
102
+ export { type AuthMessages, type AuthenticationPayload, type GoogleProfileBase, type MyGoogleProfile, type SessionProfile, defaultAuthMessages, emailDomainAllowlist, parseAllowlistCsv, resolveAuthMessages };
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Email / domain allowlist policy (transport-agnostic).
3
+ * メール / ドメインの allowlist ポリシー (トランスポート非依存)。
4
+ *
5
+ * §8-1 (統一セマンティクス): 照合は trim + lowercase で正規化する。原本の TS 側 login gate は
6
+ * 大文字小文字を区別していたが、Python 側 (access_policy) の正規化に合わせて一元化する。
7
+ * これは許可を「緩める」方向の変更で、大文字混じりの許可メールが確実に通るようになる。
8
+ */
9
+ /**
10
+ * Builds an `isEmailAllowed(email)` predicate from allowed emails and domains.
11
+ * 許可メール / 許可ドメインから `isEmailAllowed(email)` 述語を生成する。
12
+ *
13
+ * @throws when both lists are empty after normalization (fail-closed configuration guard).
14
+ * 正規化後に両リストが空なら throw (fail-closed の設定ガード)。
15
+ */
16
+ declare const emailDomainAllowlist: (allowedEmails?: readonly string[], allowedDomains?: readonly string[]) => ((email: string | null | undefined) => boolean);
17
+ /**
18
+ * Parses a comma-separated allowlist env value into a trimmed, non-empty string list.
19
+ * カンマ区切りの allowlist 環境変数値を、トリム済みかつ空要素を除いた文字列配列へ変換する。
20
+ */
21
+ declare const parseAllowlistCsv: (raw: string | null | undefined) => string[];
22
+
23
+ /**
24
+ * Localizable auth error messages shown on the login page (`/auth/login?error=...`).
25
+ * ログインページに表示する認証エラーメッセージ (上書き可能)。
26
+ *
27
+ * 既定は原本 (auth.server.ts の GoogleStrategy verify コールバック) の日本語文言と一致させる。
28
+ */
29
+ type AuthMessages = {
30
+ /** メール未確認 (email_verified === false)。 */
31
+ emailNotVerified: string;
32
+ /** Google 側での認証失敗 (トークン/プロフィール欠落)。 */
33
+ googleFailed: string;
34
+ /** バックエンド検証が false を返した。 */
35
+ backendFailed: string;
36
+ /** allowlist で許可されていないユーザー。 */
37
+ notAllowed: string;
38
+ /** バックエンドサービスへ到達できない (getaddrinfo ENOTFOUND 等)。 */
39
+ backendUnreachable: string;
40
+ /** その他の予期せぬエラー。 */
41
+ unexpected: string;
42
+ };
43
+ /** パッケージ既定の日本語メッセージ (原本と一致)。 */
44
+ declare const defaultAuthMessages: AuthMessages;
45
+ /**
46
+ * Shallow-merges a partial override over the default messages.
47
+ * 部分上書きを既定メッセージへ浅くマージする。
48
+ */
49
+ declare const resolveAuthMessages: (over?: Partial<AuthMessages>) => AuthMessages;
50
+
51
+ /**
52
+ * Transport-agnostic auth profile & session types (re-homed from the app's
53
+ * `@coji/remix-auth-google` module augmentation so non-auth code can import them
54
+ * without pulling in remix-auth or the Google SDK).
55
+ *
56
+ * `@coji/remix-auth-google` の module augmentation で生やしていた認証プロフィール型を、
57
+ * remix-auth / Google SDK 非依存で使えるよう本パッケージへ再ホームした定義。
58
+ */
59
+ /**
60
+ * Authentication token payload stored alongside the profile in the session.
61
+ * セッションにプロフィールと共に保持する認証トークンペイロード。
62
+ */
63
+ type AuthenticationPayload = {
64
+ accessToken: string;
65
+ refreshToken?: string;
66
+ expirationDateMs?: number;
67
+ provider: string;
68
+ role?: string;
69
+ };
70
+ /**
71
+ * Structural subset of the Google profile that the session/auth flow relies on.
72
+ * セッション / 認証フローが依存する Google プロフィールの構造的サブセット。
73
+ */
74
+ type GoogleProfileBase = {
75
+ id: string;
76
+ displayName: string;
77
+ name: {
78
+ familyName: string;
79
+ givenName: string;
80
+ };
81
+ emails: {
82
+ value: string;
83
+ }[];
84
+ };
85
+ /**
86
+ * The exact shape persisted in the session cookie under key "user".
87
+ * `photo` / `_json` / `photos` are intentionally excluded (byte-compatible with the
88
+ * live 30-day session cookie — see the extraction plan §1.3).
89
+ *
90
+ * セッション cookie にキー "user" で保存される正準形状。photo/_json/photos は意図的に除外
91
+ * (稼働中の 30 日 cookie と byte 互換)。
92
+ */
93
+ type SessionProfile = GoogleProfileBase & AuthenticationPayload;
94
+ /**
95
+ * The per-request user profile: session profile plus the resolved photo URL.
96
+ * リクエストごとのユーザープロフィール。セッションプロフィール + 解決済み写真 URL。
97
+ */
98
+ type MyGoogleProfile = GoogleProfileBase & {
99
+ photo: string;
100
+ } & AuthenticationPayload;
101
+
102
+ export { type AuthMessages, type AuthenticationPayload, type GoogleProfileBase, type MyGoogleProfile, type SessionProfile, defaultAuthMessages, emailDomainAllowlist, parseAllowlistCsv, resolveAuthMessages };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";var l=Object.defineProperty;var d=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var u=Object.prototype.hasOwnProperty;var m=(e,t)=>{for(var n in t)l(e,n,{get:t[n],enumerable:!0})},f=(e,t,n,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of g(t))!u.call(e,o)&&o!==n&&l(e,o,{get:()=>t[o],enumerable:!(s=d(t,o))||s.enumerable});return e};var c=e=>f(l({},"__esModule",{value:!0}),e);var x={};m(x,{defaultAuthMessages:()=>a,emailDomainAllowlist:()=>p,parseAllowlistCsv:()=>w,resolveAuthMessages:()=>h});module.exports=c(x);var p=(e=[],t=[])=>{let n=new Set(e.map(o=>o.trim().toLowerCase()).filter(Boolean)),s=new Set(t.map(o=>o.trim().toLowerCase()).filter(Boolean));if(n.size===0&&s.size===0)throw new Error("At least one of allowedEmails or allowedDomains must be set");return o=>{let r=(o??"").trim().toLowerCase();if(r==="")return!1;if(n.has(r))return!0;let i=r.lastIndexOf("@");return i>=0&&s.has(r.slice(i+1))}},w=e=>e?e.split(",").map(t=>t.trim()).filter(Boolean):[];var a={emailNotVerified:"\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u304C\u78BA\u8A8D\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",googleFailed:"Google\u3067\u306E\u8A8D\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002",backendFailed:"\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u3067\u306E\u8A8D\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002",notAllowed:"\u3053\u306E\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u3078\u306E\u30A2\u30AF\u30BB\u30B9\u306F\u8A31\u53EF\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",backendUnreachable:"\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u30B5\u30FC\u30D3\u30B9\u306B\u63A5\u7D9A\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u6642\u9593\u3092\u304A\u3044\u3066\u518D\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044\u3002",unexpected:"\u8A8D\u8A3C\u4E2D\u306B\u4E88\u671F\u305B\u306C\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002"},h=e=>e?{...a,...e}:a;0&&(module.exports={defaultAuthMessages,emailDomainAllowlist,parseAllowlistCsv,resolveAuthMessages});
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/allowlist.ts","../src/messages.ts"],"sourcesContent":["/**\n * @aiquants/auth-core — transport-agnostic auth types, allowlist policy, and messages.\n * @aiquants/auth-core — トランスポート非依存の認証型・allowlist ポリシー・メッセージ。\n */\nexport * from \"./allowlist\"\nexport * from \"./messages\"\nexport * from \"./types\"\n","/**\n * Email / domain allowlist policy (transport-agnostic).\n * メール / ドメインの allowlist ポリシー (トランスポート非依存)。\n *\n * §8-1 (統一セマンティクス): 照合は trim + lowercase で正規化する。原本の TS 側 login gate は\n * 大文字小文字を区別していたが、Python 側 (access_policy) の正規化に合わせて一元化する。\n * これは許可を「緩める」方向の変更で、大文字混じりの許可メールが確実に通るようになる。\n */\n\n/**\n * Builds an `isEmailAllowed(email)` predicate from allowed emails and domains.\n * 許可メール / 許可ドメインから `isEmailAllowed(email)` 述語を生成する。\n *\n * @throws when both lists are empty after normalization (fail-closed configuration guard).\n * 正規化後に両リストが空なら throw (fail-closed の設定ガード)。\n */\nexport const emailDomainAllowlist = (allowedEmails: readonly string[] = [], allowedDomains: readonly string[] = []): ((email: string | null | undefined) => boolean) => {\n const emails = new Set(allowedEmails.map((e) => e.trim().toLowerCase()).filter(Boolean))\n const domains = new Set(allowedDomains.map((d) => d.trim().toLowerCase()).filter(Boolean))\n if (emails.size === 0 && domains.size === 0) {\n throw new Error(\"At least one of allowedEmails or allowedDomains must be set\")\n }\n return (email: string | null | undefined): boolean => {\n const normalized = (email ?? \"\").trim().toLowerCase()\n if (normalized === \"\") return false\n if (emails.has(normalized)) return true\n // ドメイン一致: 最後の \"@\" 以降を取り出して判定する\n const at = normalized.lastIndexOf(\"@\")\n return at >= 0 && domains.has(normalized.slice(at + 1))\n }\n}\n\n/**\n * Parses a comma-separated allowlist env value into a trimmed, non-empty string list.\n * カンマ区切りの allowlist 環境変数値を、トリム済みかつ空要素を除いた文字列配列へ変換する。\n */\nexport const parseAllowlistCsv = (raw: string | null | undefined): string[] => {\n if (!raw) return []\n return raw\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean)\n}\n","/**\n * Localizable auth error messages shown on the login page (`/auth/login?error=...`).\n * ログインページに表示する認証エラーメッセージ (上書き可能)。\n *\n * 既定は原本 (auth.server.ts の GoogleStrategy verify コールバック) の日本語文言と一致させる。\n */\n\nexport type AuthMessages = {\n /** メール未確認 (email_verified === false)。 */\n emailNotVerified: string\n /** Google 側での認証失敗 (トークン/プロフィール欠落)。 */\n googleFailed: string\n /** バックエンド検証が false を返した。 */\n backendFailed: string\n /** allowlist で許可されていないユーザー。 */\n notAllowed: string\n /** バックエンドサービスへ到達できない (getaddrinfo ENOTFOUND 等)。 */\n backendUnreachable: string\n /** その他の予期せぬエラー。 */\n unexpected: string\n}\n\n/** パッケージ既定の日本語メッセージ (原本と一致)。 */\nexport const defaultAuthMessages: AuthMessages = {\n emailNotVerified: \"メールアドレスが確認されていません。\",\n googleFailed: \"Googleでの認証に失敗しました。\",\n backendFailed: \"バックエンドでの認証に失敗しました。\",\n notAllowed: \"このアプリケーションへのアクセスは許可されていません。\",\n backendUnreachable: \"バックエンドサービスに接続できませんでした。時間をおいて再度お試しください。\",\n unexpected: \"認証中に予期せぬエラーが発生しました。\",\n}\n\n/**\n * Shallow-merges a partial override over the default messages.\n * 部分上書きを既定メッセージへ浅くマージする。\n */\nexport const resolveAuthMessages = (over?: Partial<AuthMessages>): AuthMessages => (over ? { ...defaultAuthMessages, ...over } : defaultAuthMessages)\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,yBAAAE,EAAA,yBAAAC,EAAA,sBAAAC,EAAA,wBAAAC,IAAA,eAAAC,EAAAN,GCgBO,IAAMO,EAAuB,CAACC,EAAmC,CAAC,EAAGC,EAAoC,CAAC,IAAuD,CACpK,IAAMC,EAAS,IAAI,IAAIF,EAAc,IAAKG,GAAMA,EAAE,KAAK,EAAE,YAAY,CAAC,EAAE,OAAO,OAAO,CAAC,EACjFC,EAAU,IAAI,IAAIH,EAAe,IAAKI,GAAMA,EAAE,KAAK,EAAE,YAAY,CAAC,EAAE,OAAO,OAAO,CAAC,EACzF,GAAIH,EAAO,OAAS,GAAKE,EAAQ,OAAS,EACtC,MAAM,IAAI,MAAM,6DAA6D,EAEjF,OAAQE,GAA8C,CAClD,IAAMC,GAAcD,GAAS,IAAI,KAAK,EAAE,YAAY,EACpD,GAAIC,IAAe,GAAI,MAAO,GAC9B,GAAIL,EAAO,IAAIK,CAAU,EAAG,MAAO,GAEnC,IAAMC,EAAKD,EAAW,YAAY,GAAG,EACrC,OAAOC,GAAM,GAAKJ,EAAQ,IAAIG,EAAW,MAAMC,EAAK,CAAC,CAAC,CAC1D,CACJ,EAMaC,EAAqBC,GACzBA,EACEA,EACF,MAAM,GAAG,EACT,IAAKC,GAAMA,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO,EAJF,CAAC,ECdf,IAAMC,EAAoC,CAC7C,iBAAkB,+GAClB,aAAc,iFACd,cAAe,+GACf,WAAY,qKACZ,mBAAoB,uOACpB,WAAY,oHAChB,EAMaC,EAAuBC,GAAgDA,EAAO,CAAE,GAAGF,EAAqB,GAAGE,CAAK,EAAIF","names":["index_exports","__export","defaultAuthMessages","emailDomainAllowlist","parseAllowlistCsv","resolveAuthMessages","__toCommonJS","emailDomainAllowlist","allowedEmails","allowedDomains","emails","e","domains","d","email","normalized","at","parseAllowlistCsv","raw","s","defaultAuthMessages","resolveAuthMessages","over"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ var i=(e=[],n=[])=>{let s=new Set(e.map(t=>t.trim().toLowerCase()).filter(Boolean)),r=new Set(n.map(t=>t.trim().toLowerCase()).filter(Boolean));if(s.size===0&&r.size===0)throw new Error("At least one of allowedEmails or allowedDomains must be set");return t=>{let o=(t??"").trim().toLowerCase();if(o==="")return!1;if(s.has(o))return!0;let l=o.lastIndexOf("@");return l>=0&&r.has(o.slice(l+1))}},d=e=>e?e.split(",").map(n=>n.trim()).filter(Boolean):[];var a={emailNotVerified:"\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u304C\u78BA\u8A8D\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",googleFailed:"Google\u3067\u306E\u8A8D\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002",backendFailed:"\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u3067\u306E\u8A8D\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002",notAllowed:"\u3053\u306E\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u3078\u306E\u30A2\u30AF\u30BB\u30B9\u306F\u8A31\u53EF\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",backendUnreachable:"\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u30B5\u30FC\u30D3\u30B9\u306B\u63A5\u7D9A\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u6642\u9593\u3092\u304A\u3044\u3066\u518D\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044\u3002",unexpected:"\u8A8D\u8A3C\u4E2D\u306B\u4E88\u671F\u305B\u306C\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002"},u=e=>e?{...a,...e}:a;export{a as defaultAuthMessages,i as emailDomainAllowlist,d as parseAllowlistCsv,u as resolveAuthMessages};
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/allowlist.ts","../src/messages.ts"],"sourcesContent":["/**\n * Email / domain allowlist policy (transport-agnostic).\n * メール / ドメインの allowlist ポリシー (トランスポート非依存)。\n *\n * §8-1 (統一セマンティクス): 照合は trim + lowercase で正規化する。原本の TS 側 login gate は\n * 大文字小文字を区別していたが、Python 側 (access_policy) の正規化に合わせて一元化する。\n * これは許可を「緩める」方向の変更で、大文字混じりの許可メールが確実に通るようになる。\n */\n\n/**\n * Builds an `isEmailAllowed(email)` predicate from allowed emails and domains.\n * 許可メール / 許可ドメインから `isEmailAllowed(email)` 述語を生成する。\n *\n * @throws when both lists are empty after normalization (fail-closed configuration guard).\n * 正規化後に両リストが空なら throw (fail-closed の設定ガード)。\n */\nexport const emailDomainAllowlist = (allowedEmails: readonly string[] = [], allowedDomains: readonly string[] = []): ((email: string | null | undefined) => boolean) => {\n const emails = new Set(allowedEmails.map((e) => e.trim().toLowerCase()).filter(Boolean))\n const domains = new Set(allowedDomains.map((d) => d.trim().toLowerCase()).filter(Boolean))\n if (emails.size === 0 && domains.size === 0) {\n throw new Error(\"At least one of allowedEmails or allowedDomains must be set\")\n }\n return (email: string | null | undefined): boolean => {\n const normalized = (email ?? \"\").trim().toLowerCase()\n if (normalized === \"\") return false\n if (emails.has(normalized)) return true\n // ドメイン一致: 最後の \"@\" 以降を取り出して判定する\n const at = normalized.lastIndexOf(\"@\")\n return at >= 0 && domains.has(normalized.slice(at + 1))\n }\n}\n\n/**\n * Parses a comma-separated allowlist env value into a trimmed, non-empty string list.\n * カンマ区切りの allowlist 環境変数値を、トリム済みかつ空要素を除いた文字列配列へ変換する。\n */\nexport const parseAllowlistCsv = (raw: string | null | undefined): string[] => {\n if (!raw) return []\n return raw\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean)\n}\n","/**\n * Localizable auth error messages shown on the login page (`/auth/login?error=...`).\n * ログインページに表示する認証エラーメッセージ (上書き可能)。\n *\n * 既定は原本 (auth.server.ts の GoogleStrategy verify コールバック) の日本語文言と一致させる。\n */\n\nexport type AuthMessages = {\n /** メール未確認 (email_verified === false)。 */\n emailNotVerified: string\n /** Google 側での認証失敗 (トークン/プロフィール欠落)。 */\n googleFailed: string\n /** バックエンド検証が false を返した。 */\n backendFailed: string\n /** allowlist で許可されていないユーザー。 */\n notAllowed: string\n /** バックエンドサービスへ到達できない (getaddrinfo ENOTFOUND 等)。 */\n backendUnreachable: string\n /** その他の予期せぬエラー。 */\n unexpected: string\n}\n\n/** パッケージ既定の日本語メッセージ (原本と一致)。 */\nexport const defaultAuthMessages: AuthMessages = {\n emailNotVerified: \"メールアドレスが確認されていません。\",\n googleFailed: \"Googleでの認証に失敗しました。\",\n backendFailed: \"バックエンドでの認証に失敗しました。\",\n notAllowed: \"このアプリケーションへのアクセスは許可されていません。\",\n backendUnreachable: \"バックエンドサービスに接続できませんでした。時間をおいて再度お試しください。\",\n unexpected: \"認証中に予期せぬエラーが発生しました。\",\n}\n\n/**\n * Shallow-merges a partial override over the default messages.\n * 部分上書きを既定メッセージへ浅くマージする。\n */\nexport const resolveAuthMessages = (over?: Partial<AuthMessages>): AuthMessages => (over ? { ...defaultAuthMessages, ...over } : defaultAuthMessages)\n"],"mappings":"AAgBO,IAAMA,EAAuB,CAACC,EAAmC,CAAC,EAAGC,EAAoC,CAAC,IAAuD,CACpK,IAAMC,EAAS,IAAI,IAAIF,EAAc,IAAKG,GAAMA,EAAE,KAAK,EAAE,YAAY,CAAC,EAAE,OAAO,OAAO,CAAC,EACjFC,EAAU,IAAI,IAAIH,EAAe,IAAKI,GAAMA,EAAE,KAAK,EAAE,YAAY,CAAC,EAAE,OAAO,OAAO,CAAC,EACzF,GAAIH,EAAO,OAAS,GAAKE,EAAQ,OAAS,EACtC,MAAM,IAAI,MAAM,6DAA6D,EAEjF,OAAQE,GAA8C,CAClD,IAAMC,GAAcD,GAAS,IAAI,KAAK,EAAE,YAAY,EACpD,GAAIC,IAAe,GAAI,MAAO,GAC9B,GAAIL,EAAO,IAAIK,CAAU,EAAG,MAAO,GAEnC,IAAMC,EAAKD,EAAW,YAAY,GAAG,EACrC,OAAOC,GAAM,GAAKJ,EAAQ,IAAIG,EAAW,MAAMC,EAAK,CAAC,CAAC,CAC1D,CACJ,EAMaC,EAAqBC,GACzBA,EACEA,EACF,MAAM,GAAG,EACT,IAAKC,GAAMA,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO,EAJF,CAAC,ECdf,IAAMC,EAAoC,CAC7C,iBAAkB,+GAClB,aAAc,iFACd,cAAe,+GACf,WAAY,qKACZ,mBAAoB,uOACpB,WAAY,oHAChB,EAMaC,EAAuBC,GAAgDA,EAAO,CAAE,GAAGF,EAAqB,GAAGE,CAAK,EAAIF","names":["emailDomainAllowlist","allowedEmails","allowedDomains","emails","e","domains","d","email","normalized","at","parseAllowlistCsv","raw","s","defaultAuthMessages","resolveAuthMessages","over"]}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@aiquants/auth-core",
3
+ "version": "0.1.1",
4
+ "description": "Transport-agnostic auth core: shared session/profile types, email/domain allowlist policy, and localizable auth error messages. No framework or provider SDK dependency.",
5
+ "sideEffects": false,
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.mjs",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "keywords": [
22
+ "auth",
23
+ "authentication",
24
+ "session",
25
+ "allowlist",
26
+ "typescript"
27
+ ],
28
+ "author": {
29
+ "name": "fehde-k",
30
+ "url": "https://x.com/fehdek"
31
+ },
32
+ "license": "MIT",
33
+ "devDependencies": {
34
+ "rimraf": "^6.1.2",
35
+ "tsup": "^8.5.1",
36
+ "typescript": "^5.9.3",
37
+ "vitest": "^4.1.8"
38
+ },
39
+ "engines": {
40
+ "node": ">=18.0.0",
41
+ "pnpm": ">=8.0.0"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "scripts": {
47
+ "build": "tsup",
48
+ "build:watch": "tsup --watch",
49
+ "dev": "tsup --watch",
50
+ "typecheck": "tsc --noEmit",
51
+ "clean": "rimraf dist",
52
+ "publish:patch": "pnpm run typecheck && pnpm run --if-present test && pnpm version patch --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks",
53
+ "publish:minor": "pnpm run typecheck && pnpm run --if-present test && pnpm version minor --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks",
54
+ "publish:major": "pnpm run typecheck && pnpm run --if-present test && pnpm version major --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks",
55
+ "lint": "biome lint src/",
56
+ "check": "biome check src/",
57
+ "check:fix": "biome check --write src/",
58
+ "test": "vitest run",
59
+ "test:coverage": "vitest run --coverage"
60
+ }
61
+ }