@aiquants/auth-core 0.1.1 → 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/README.md CHANGED
@@ -14,6 +14,14 @@ Inside this monorepo it is already wired as a pnpm workspace package:
14
14
 
15
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
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
+ Domain patterns accept **both** `example.com` and `@example.com` (a leading `@` is stripped), because env values are usually written bare while admin UIs tend to teach the `@`-prefixed form. Matching is exact on the host — `example.com` never grants `evil-example.com` or `sub.example.com`.
18
+ - `normalizeAuthEmail(email)` — the single comparison form (`trim` + `toLowerCase`) shared by the allowlist predicate, the external directory ledger, and membership matching. Two normalizers would let an address be present in one surface and absent in another with nothing raising an error.
19
+ - `AuthUser` / `AuthGroup` / `AuthGroupMember` / `AuthAllowlistEntry` — entity types for a user/group administration surface (see `@aiquants/auth-react-router/admin`).
20
+ - `AuthGroup.externalLink` (`AuthGroupExternalLink`) records that an upstream directory owns the group's name and membership; `AuthGroup.readOnly` is the flag the UI acts on. Role assignment stays local, so an upstream change never grants privileges by itself.
21
+ - `AuthAllowlistEntry` is a discriminated union on `type`: `email` / `domain` rows carry a literal `pattern`, `group` rows carry a `groupId`. A group's address can be renamed upstream, so it is never the canonical value.
22
+ - `AuthAllowlistEntry.owner` says **who may edit** the row (`deployment` = immutable env-var source, `tenant` = that tenant's administrators). It is deliberately not a login-time filter: authentication happens before a tenant is resolved.
23
+ - `readOnly` is required on both types. Making it optional would introduce an implicit "unset means editable" default.
24
+ - `AuthDirectoryStatus` — health of the external directory synchronization. `isStale` is `true` when a sync has never succeeded, because "unknown" is not evidence of health.
17
25
  - `defaultAuthMessages` / `AuthMessages` / `resolveAuthMessages(over?)` — the Japanese login-page error strings, overridable per app.
18
26
 
19
27
  ## Session profile shape (byte-compatible contract)
package/dist/index.d.mts CHANGED
@@ -6,6 +6,21 @@
6
6
  * 大文字小文字を区別していたが、Python 側 (access_policy) の正規化に合わせて一元化する。
7
7
  * これは許可を「緩める」方向の変更で、大文字混じりの許可メールが確実に通るようになる。
8
8
  */
9
+ /**
10
+ * Normalizes an address for comparison: trims surrounding whitespace and lowercases.
11
+ * 照合用にアドレスを正規化する処理。前後の空白を除去し小文字化する。
12
+ *
13
+ * **この 1 本が照合形の唯一の出所である。** 許可リスト・外部ディレクトリ台帳・所属の突合は
14
+ * すべてこれを通すこと。片側だけが別の正規化を持つと、台帳には載っているのに許可されない
15
+ * (あるいはその逆の) 食い違いが、どこにもエラーを出さないまま成立する。
16
+ *
17
+ * Python 側の姉妹実装 `access_policy.py` と同一セマンティクスであることが契約 (設計仕様書 §1-3)。
18
+ *
19
+ * @param email - Raw address, possibly absent. 生のアドレス (未設定もあり得る)。
20
+ * @returns The comparison form; empty string when there is nothing to compare.
21
+ * 照合形。照合対象が無い場合は空文字。
22
+ */
23
+ declare const normalizeAuthEmail: (email: string | null | undefined) => string;
9
24
  /**
10
25
  * Builds an `isEmailAllowed(email)` predicate from allowed emails and domains.
11
26
  * 許可メール / 許可ドメインから `isEmailAllowed(email)` 述語を生成する。
@@ -98,5 +113,189 @@ type SessionProfile = GoogleProfileBase & AuthenticationPayload;
98
113
  type MyGoogleProfile = GoogleProfileBase & {
99
114
  photo: string;
100
115
  } & AuthenticationPayload;
116
+ /**
117
+ * Managed user entity in the authentication system.
118
+ * 認証システムで管理されるユーザーエンティティ。
119
+ */
120
+ type AuthUser = {
121
+ id: number;
122
+ email: string;
123
+ displayName: string;
124
+ photo?: string;
125
+ isActive: boolean;
126
+ createdAt?: string;
127
+ updatedAt?: string;
128
+ };
129
+ /**
130
+ * How an upstream directory's group membership is expanded.
131
+ * 上流ディレクトリのグループ所属をどこまで展開するか。
132
+ *
133
+ * `direct` はそのグループへ直接所属するメンバーのみ、`transitive` は入れ子グループを
134
+ * 展開した全メンバーを対象とする。既定値は設けない — 入れ子展開が黙って有効になると
135
+ * 権限が静かに広がるため、供給元ごとに明示させる。
136
+ */
137
+ type AuthDirectoryMembershipMode = "direct" | "transitive";
138
+ /**
139
+ * Provenance of a group whose membership is owned by an upstream directory.
140
+ * 所属の所有者が上流ディレクトリにあるグループの出所。
141
+ *
142
+ * この値を持つグループは名称・所属の双方を上流が所有するため、管理 UI は編集を拒否する。
143
+ * ロール割当 (どの権限を与えるか) は引き続きローカルが所有する — 上流の変更が権限へ
144
+ * 直接波及しない境界を保つため。
145
+ */
146
+ type AuthGroupExternalLink = {
147
+ /** Upstream system identifier, e.g. `"google"`. 上流システムの識別子。 */
148
+ provider: string;
149
+ /**
150
+ * Stable upstream id. 上流の安定 ID。
151
+ *
152
+ * 表示用のアドレス (`externalKey`) は改名され得るため、突合は必ずこちらで行う。
153
+ */
154
+ externalId: string;
155
+ /** Operator-facing key such as the group address. 運用者向けキー (グループアドレス等)。 */
156
+ externalKey: string;
157
+ membershipMode: AuthDirectoryMembershipMode;
158
+ /** False pauses synchronization for this group alone. この 1 グループだけ同期を止める。 */
159
+ isSyncEnabled: boolean;
160
+ /** Last successful reconciliation; absent when none has ever succeeded. 最終同期成功時刻。 */
161
+ syncedAt?: string;
162
+ /** Message of the last failed reconciliation. 直近の同期失敗の理由。 */
163
+ syncError?: string;
164
+ /**
165
+ * Members observed upstream that no local user could be resolved for.
166
+ * 上流に居るがローカルユーザーへ解決できなかったメンバーの件数。
167
+ *
168
+ * 未ログインの人物は意図的にユーザー行を作らないため、この件数はエラーではなく
169
+ * 「まだ権限が付いていない人が居る」ことの可視化である。
170
+ */
171
+ unresolvedCount: number;
172
+ };
173
+ /**
174
+ * User group entity for aggregating users into teams or departments.
175
+ * ユーザーをチームや部署単位で集約するユーザーグループエンティティ。
176
+ */
177
+ type AuthGroup = {
178
+ id: number;
179
+ groupKey: string;
180
+ name: string;
181
+ description?: string;
182
+ isActive: boolean;
183
+ /**
184
+ * Set when membership comes from an upstream directory; absent means locally managed.
185
+ * 所属が上流ディレクトリ由来である場合に設定される。未設定はローカル管理を意味する。
186
+ */
187
+ externalLink?: AuthGroupExternalLink;
188
+ /**
189
+ * True when the admin UI must refuse edits to this group and its membership.
190
+ * このグループとその所属に対する編集を管理 UI が拒否すべき場合に true。
191
+ *
192
+ * 任意ではなく必須にしてある。任意にすると「未設定 = 編集可」という暗黙の既定が生まれ、
193
+ * 供給元の設定を書き忘れた行が編集可能として描かれてしまう。
194
+ */
195
+ readOnly: boolean;
196
+ createdAt?: string;
197
+ updatedAt?: string;
198
+ };
199
+ /**
200
+ * Membership link between a user and a user group.
201
+ * ユーザーとユーザーグループの所属紐付けエンティティ。
202
+ */
203
+ type AuthGroupMember = {
204
+ id: number;
205
+ groupId: number;
206
+ userId: number;
207
+ createdAt?: string;
208
+ };
209
+ /**
210
+ * Who owns an allowlist entry — that is, who may edit it.
211
+ * アロワリスト 1 行の所有者、すなわち誰が編集してよいかを表す値。
212
+ *
213
+ * ⚠️ **ログイン判定の絞り込み条件ではない。** 認証はテナントの確定より前に起きるため、
214
+ * 判定の時点でテナントは存在しない。許可判定はこの配備が受け持つ全テナントの和集合で行い、
215
+ * この値は管理 UI の編集スコープだけを決める。
216
+ */
217
+ type AuthAllowlistOwner =
218
+ /** Supplied by immutable deployment configuration (environment variables). 配備設定由来。 */
219
+ {
220
+ kind: "deployment";
221
+ }
222
+ /** Owned by one tenant's administrators. 単一テナントの管理者が所有。 */
223
+ | {
224
+ kind: "tenant";
225
+ tenantId: string;
226
+ };
227
+ /** Fields every allowlist entry carries regardless of its kind. 種別によらず共通の項目。 */
228
+ type AuthAllowlistEntryBase = {
229
+ id: number;
230
+ owner: AuthAllowlistOwner;
231
+ description?: string;
232
+ createdAt?: string;
233
+ /**
234
+ * True when the entry cannot be edited or deleted through the admin UI.
235
+ * 管理 UI から編集・削除できないエントリの目印。
236
+ *
237
+ * 環境変数など変更不能な供給元に由来する行が該当する。必須項目である理由は
238
+ * {@link AuthGroup.readOnly} と同じ (未設定を「編集可」と読み替えさせないため)。
239
+ */
240
+ readOnly: boolean;
241
+ };
242
+ /**
243
+ * Allowlist entry defining who may sign in.
244
+ * サインインを許可する対象を定義するアロワリストエントリ。
245
+ *
246
+ * 判別共用体にしてあるのは、グループ許可の実体が **グループ ID** であり、改名され得る
247
+ * グループアドレスを正準値として持たせられないためである。単一の `pattern: string` へ
248
+ * 押し込むと、上流でアドレスが変わった瞬間に許可が静かに失われる。
249
+ */
250
+ type AuthAllowlistEntry = (AuthAllowlistEntryBase & {
251
+ type: "email" | "domain";
252
+ /** The literal address or bare host to match. 照合するアドレスまたは裸ホスト。 */
253
+ pattern: string;
254
+ }) | (AuthAllowlistEntryBase & {
255
+ type: "group";
256
+ /** Local group id the permission is anchored to. 許可の基点となるローカルグループ ID。 */
257
+ groupId: number;
258
+ groupKey: string;
259
+ groupName: string;
260
+ });
261
+ /**
262
+ * Health of the external directory synchronization, surfaced in the admin UI.
263
+ * 管理 UI に表示する外部ディレクトリ同期の健全性。
264
+ *
265
+ * 同期が別プロセスで動くため、web 側からは生死が見えない。テナント帰属証明が同期結果に
266
+ * 依存する構成では、同期の停止は「退職者がテナントに居続ける」というセキュリティ事象に
267
+ * なるため、陳腐化の可視化は任意ではなく必須である。
268
+ */
269
+ type AuthDirectoryStatus = {
270
+ /** False when directory integration is not wired for this deployment. 未結線なら false。 */
271
+ isEnabled: boolean;
272
+ /** Number of groups whose membership is externally sourced. 外部供給グループの件数。 */
273
+ linkedGroupCount: number;
274
+ /**
275
+ * Oldest successful sync among sync-enabled links; absent when none has ever succeeded.
276
+ * 同期有効なリンクのうち最も古い成功時刻。一度も成功していない場合は未設定。
277
+ */
278
+ oldestSyncedAt?: string;
279
+ /**
280
+ * True when synchronization is overdue — including the case where it has never succeeded.
281
+ * 同期が期限超過している場合に true。一度も成功していない場合も含む。
282
+ *
283
+ * 「不明」を健全側へ倒さない。判定できないことは、健全であることの証明ではない。
284
+ */
285
+ isStale: boolean;
286
+ /**
287
+ * Linked groups whose synchronization is paused. 同期を停止しているリンク済みグループ数。
288
+ *
289
+ * リンクがあるのに 1 本も有効でない状態は健全ではない。台帳は凍結したまま権限を配り
290
+ * 続けるのに、件数とエラー数だけを見ると何も起きていないように読めてしまう。
291
+ */
292
+ pausedGroupCount: number;
293
+ /** Age in seconds beyond which a sync counts as overdue. 期限超過とみなす経過秒数。 */
294
+ stalenessThresholdSec: number;
295
+ /** Groups whose last synchronization failed. 直近の同期に失敗したグループ。 */
296
+ erroredGroupIds: number[];
297
+ /** Sync requests queued but not yet processed. 未処理の同期要求件数。 */
298
+ pendingRequestCount: number;
299
+ };
101
300
 
102
- export { type AuthMessages, type AuthenticationPayload, type GoogleProfileBase, type MyGoogleProfile, type SessionProfile, defaultAuthMessages, emailDomainAllowlist, parseAllowlistCsv, resolveAuthMessages };
301
+ export { type AuthAllowlistEntry, type AuthAllowlistOwner, type AuthDirectoryMembershipMode, type AuthDirectoryStatus, type AuthGroup, type AuthGroupExternalLink, type AuthGroupMember, type AuthMessages, type AuthUser, type AuthenticationPayload, type GoogleProfileBase, type MyGoogleProfile, type SessionProfile, defaultAuthMessages, emailDomainAllowlist, normalizeAuthEmail, parseAllowlistCsv, resolveAuthMessages };
package/dist/index.d.ts CHANGED
@@ -6,6 +6,21 @@
6
6
  * 大文字小文字を区別していたが、Python 側 (access_policy) の正規化に合わせて一元化する。
7
7
  * これは許可を「緩める」方向の変更で、大文字混じりの許可メールが確実に通るようになる。
8
8
  */
9
+ /**
10
+ * Normalizes an address for comparison: trims surrounding whitespace and lowercases.
11
+ * 照合用にアドレスを正規化する処理。前後の空白を除去し小文字化する。
12
+ *
13
+ * **この 1 本が照合形の唯一の出所である。** 許可リスト・外部ディレクトリ台帳・所属の突合は
14
+ * すべてこれを通すこと。片側だけが別の正規化を持つと、台帳には載っているのに許可されない
15
+ * (あるいはその逆の) 食い違いが、どこにもエラーを出さないまま成立する。
16
+ *
17
+ * Python 側の姉妹実装 `access_policy.py` と同一セマンティクスであることが契約 (設計仕様書 §1-3)。
18
+ *
19
+ * @param email - Raw address, possibly absent. 生のアドレス (未設定もあり得る)。
20
+ * @returns The comparison form; empty string when there is nothing to compare.
21
+ * 照合形。照合対象が無い場合は空文字。
22
+ */
23
+ declare const normalizeAuthEmail: (email: string | null | undefined) => string;
9
24
  /**
10
25
  * Builds an `isEmailAllowed(email)` predicate from allowed emails and domains.
11
26
  * 許可メール / 許可ドメインから `isEmailAllowed(email)` 述語を生成する。
@@ -98,5 +113,189 @@ type SessionProfile = GoogleProfileBase & AuthenticationPayload;
98
113
  type MyGoogleProfile = GoogleProfileBase & {
99
114
  photo: string;
100
115
  } & AuthenticationPayload;
116
+ /**
117
+ * Managed user entity in the authentication system.
118
+ * 認証システムで管理されるユーザーエンティティ。
119
+ */
120
+ type AuthUser = {
121
+ id: number;
122
+ email: string;
123
+ displayName: string;
124
+ photo?: string;
125
+ isActive: boolean;
126
+ createdAt?: string;
127
+ updatedAt?: string;
128
+ };
129
+ /**
130
+ * How an upstream directory's group membership is expanded.
131
+ * 上流ディレクトリのグループ所属をどこまで展開するか。
132
+ *
133
+ * `direct` はそのグループへ直接所属するメンバーのみ、`transitive` は入れ子グループを
134
+ * 展開した全メンバーを対象とする。既定値は設けない — 入れ子展開が黙って有効になると
135
+ * 権限が静かに広がるため、供給元ごとに明示させる。
136
+ */
137
+ type AuthDirectoryMembershipMode = "direct" | "transitive";
138
+ /**
139
+ * Provenance of a group whose membership is owned by an upstream directory.
140
+ * 所属の所有者が上流ディレクトリにあるグループの出所。
141
+ *
142
+ * この値を持つグループは名称・所属の双方を上流が所有するため、管理 UI は編集を拒否する。
143
+ * ロール割当 (どの権限を与えるか) は引き続きローカルが所有する — 上流の変更が権限へ
144
+ * 直接波及しない境界を保つため。
145
+ */
146
+ type AuthGroupExternalLink = {
147
+ /** Upstream system identifier, e.g. `"google"`. 上流システムの識別子。 */
148
+ provider: string;
149
+ /**
150
+ * Stable upstream id. 上流の安定 ID。
151
+ *
152
+ * 表示用のアドレス (`externalKey`) は改名され得るため、突合は必ずこちらで行う。
153
+ */
154
+ externalId: string;
155
+ /** Operator-facing key such as the group address. 運用者向けキー (グループアドレス等)。 */
156
+ externalKey: string;
157
+ membershipMode: AuthDirectoryMembershipMode;
158
+ /** False pauses synchronization for this group alone. この 1 グループだけ同期を止める。 */
159
+ isSyncEnabled: boolean;
160
+ /** Last successful reconciliation; absent when none has ever succeeded. 最終同期成功時刻。 */
161
+ syncedAt?: string;
162
+ /** Message of the last failed reconciliation. 直近の同期失敗の理由。 */
163
+ syncError?: string;
164
+ /**
165
+ * Members observed upstream that no local user could be resolved for.
166
+ * 上流に居るがローカルユーザーへ解決できなかったメンバーの件数。
167
+ *
168
+ * 未ログインの人物は意図的にユーザー行を作らないため、この件数はエラーではなく
169
+ * 「まだ権限が付いていない人が居る」ことの可視化である。
170
+ */
171
+ unresolvedCount: number;
172
+ };
173
+ /**
174
+ * User group entity for aggregating users into teams or departments.
175
+ * ユーザーをチームや部署単位で集約するユーザーグループエンティティ。
176
+ */
177
+ type AuthGroup = {
178
+ id: number;
179
+ groupKey: string;
180
+ name: string;
181
+ description?: string;
182
+ isActive: boolean;
183
+ /**
184
+ * Set when membership comes from an upstream directory; absent means locally managed.
185
+ * 所属が上流ディレクトリ由来である場合に設定される。未設定はローカル管理を意味する。
186
+ */
187
+ externalLink?: AuthGroupExternalLink;
188
+ /**
189
+ * True when the admin UI must refuse edits to this group and its membership.
190
+ * このグループとその所属に対する編集を管理 UI が拒否すべき場合に true。
191
+ *
192
+ * 任意ではなく必須にしてある。任意にすると「未設定 = 編集可」という暗黙の既定が生まれ、
193
+ * 供給元の設定を書き忘れた行が編集可能として描かれてしまう。
194
+ */
195
+ readOnly: boolean;
196
+ createdAt?: string;
197
+ updatedAt?: string;
198
+ };
199
+ /**
200
+ * Membership link between a user and a user group.
201
+ * ユーザーとユーザーグループの所属紐付けエンティティ。
202
+ */
203
+ type AuthGroupMember = {
204
+ id: number;
205
+ groupId: number;
206
+ userId: number;
207
+ createdAt?: string;
208
+ };
209
+ /**
210
+ * Who owns an allowlist entry — that is, who may edit it.
211
+ * アロワリスト 1 行の所有者、すなわち誰が編集してよいかを表す値。
212
+ *
213
+ * ⚠️ **ログイン判定の絞り込み条件ではない。** 認証はテナントの確定より前に起きるため、
214
+ * 判定の時点でテナントは存在しない。許可判定はこの配備が受け持つ全テナントの和集合で行い、
215
+ * この値は管理 UI の編集スコープだけを決める。
216
+ */
217
+ type AuthAllowlistOwner =
218
+ /** Supplied by immutable deployment configuration (environment variables). 配備設定由来。 */
219
+ {
220
+ kind: "deployment";
221
+ }
222
+ /** Owned by one tenant's administrators. 単一テナントの管理者が所有。 */
223
+ | {
224
+ kind: "tenant";
225
+ tenantId: string;
226
+ };
227
+ /** Fields every allowlist entry carries regardless of its kind. 種別によらず共通の項目。 */
228
+ type AuthAllowlistEntryBase = {
229
+ id: number;
230
+ owner: AuthAllowlistOwner;
231
+ description?: string;
232
+ createdAt?: string;
233
+ /**
234
+ * True when the entry cannot be edited or deleted through the admin UI.
235
+ * 管理 UI から編集・削除できないエントリの目印。
236
+ *
237
+ * 環境変数など変更不能な供給元に由来する行が該当する。必須項目である理由は
238
+ * {@link AuthGroup.readOnly} と同じ (未設定を「編集可」と読み替えさせないため)。
239
+ */
240
+ readOnly: boolean;
241
+ };
242
+ /**
243
+ * Allowlist entry defining who may sign in.
244
+ * サインインを許可する対象を定義するアロワリストエントリ。
245
+ *
246
+ * 判別共用体にしてあるのは、グループ許可の実体が **グループ ID** であり、改名され得る
247
+ * グループアドレスを正準値として持たせられないためである。単一の `pattern: string` へ
248
+ * 押し込むと、上流でアドレスが変わった瞬間に許可が静かに失われる。
249
+ */
250
+ type AuthAllowlistEntry = (AuthAllowlistEntryBase & {
251
+ type: "email" | "domain";
252
+ /** The literal address or bare host to match. 照合するアドレスまたは裸ホスト。 */
253
+ pattern: string;
254
+ }) | (AuthAllowlistEntryBase & {
255
+ type: "group";
256
+ /** Local group id the permission is anchored to. 許可の基点となるローカルグループ ID。 */
257
+ groupId: number;
258
+ groupKey: string;
259
+ groupName: string;
260
+ });
261
+ /**
262
+ * Health of the external directory synchronization, surfaced in the admin UI.
263
+ * 管理 UI に表示する外部ディレクトリ同期の健全性。
264
+ *
265
+ * 同期が別プロセスで動くため、web 側からは生死が見えない。テナント帰属証明が同期結果に
266
+ * 依存する構成では、同期の停止は「退職者がテナントに居続ける」というセキュリティ事象に
267
+ * なるため、陳腐化の可視化は任意ではなく必須である。
268
+ */
269
+ type AuthDirectoryStatus = {
270
+ /** False when directory integration is not wired for this deployment. 未結線なら false。 */
271
+ isEnabled: boolean;
272
+ /** Number of groups whose membership is externally sourced. 外部供給グループの件数。 */
273
+ linkedGroupCount: number;
274
+ /**
275
+ * Oldest successful sync among sync-enabled links; absent when none has ever succeeded.
276
+ * 同期有効なリンクのうち最も古い成功時刻。一度も成功していない場合は未設定。
277
+ */
278
+ oldestSyncedAt?: string;
279
+ /**
280
+ * True when synchronization is overdue — including the case where it has never succeeded.
281
+ * 同期が期限超過している場合に true。一度も成功していない場合も含む。
282
+ *
283
+ * 「不明」を健全側へ倒さない。判定できないことは、健全であることの証明ではない。
284
+ */
285
+ isStale: boolean;
286
+ /**
287
+ * Linked groups whose synchronization is paused. 同期を停止しているリンク済みグループ数。
288
+ *
289
+ * リンクがあるのに 1 本も有効でない状態は健全ではない。台帳は凍結したまま権限を配り
290
+ * 続けるのに、件数とエラー数だけを見ると何も起きていないように読めてしまう。
291
+ */
292
+ pausedGroupCount: number;
293
+ /** Age in seconds beyond which a sync counts as overdue. 期限超過とみなす経過秒数。 */
294
+ stalenessThresholdSec: number;
295
+ /** Groups whose last synchronization failed. 直近の同期に失敗したグループ。 */
296
+ erroredGroupIds: number[];
297
+ /** Sync requests queued but not yet processed. 未処理の同期要求件数。 */
298
+ pendingRequestCount: number;
299
+ };
101
300
 
102
- export { type AuthMessages, type AuthenticationPayload, type GoogleProfileBase, type MyGoogleProfile, type SessionProfile, defaultAuthMessages, emailDomainAllowlist, parseAllowlistCsv, resolveAuthMessages };
301
+ export { type AuthAllowlistEntry, type AuthAllowlistOwner, type AuthDirectoryMembershipMode, type AuthDirectoryStatus, type AuthGroup, type AuthGroupExternalLink, type AuthGroupMember, type AuthMessages, type AuthUser, type AuthenticationPayload, type GoogleProfileBase, type MyGoogleProfile, type SessionProfile, defaultAuthMessages, emailDomainAllowlist, normalizeAuthEmail, parseAllowlistCsv, resolveAuthMessages };
package/dist/index.js CHANGED
@@ -1,2 +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});
1
+ "use strict";var i=Object.defineProperty;var u=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var m=Object.prototype.hasOwnProperty;var f=(e,t)=>{for(var s in t)i(e,s,{get:t[s],enumerable:!0})},c=(e,t,s,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of g(t))!m.call(e,n)&&n!==s&&i(e,n,{get:()=>t[n],enumerable:!(o=u(t,n))||o.enumerable});return e};var p=e=>c(i({},"__esModule",{value:!0}),e);var w={};f(w,{defaultAuthMessages:()=>a,emailDomainAllowlist:()=>h,normalizeAuthEmail:()=>l,parseAllowlistCsv:()=>x,resolveAuthMessages:()=>A});module.exports=p(w);var l=e=>(e??"").trim().toLowerCase(),h=(e=[],t=[])=>{let s=new Set(e.map(l).filter(Boolean)),o=new Set(t.map(n=>l(n).replace(/^@+/,"")).filter(Boolean));if(s.size===0&&o.size===0)throw new Error("At least one of allowedEmails or allowedDomains must be set");return n=>{let r=l(n);if(r==="")return!1;if(s.has(r))return!0;let d=r.lastIndexOf("@");return d>=0&&o.has(r.slice(d+1))}},x=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"},A=e=>e?{...a,...e}:a;0&&(module.exports={defaultAuthMessages,emailDomainAllowlist,normalizeAuthEmail,parseAllowlistCsv,resolveAuthMessages});
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +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"]}
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 * Normalizes an address for comparison: trims surrounding whitespace and lowercases.\n * 照合用にアドレスを正規化する処理。前後の空白を除去し小文字化する。\n *\n * **この 1 本が照合形の唯一の出所である。** 許可リスト・外部ディレクトリ台帳・所属の突合は\n * すべてこれを通すこと。片側だけが別の正規化を持つと、台帳には載っているのに許可されない\n * (あるいはその逆の) 食い違いが、どこにもエラーを出さないまま成立する。\n *\n * Python 側の姉妹実装 `access_policy.py` と同一セマンティクスであることが契約 (設計仕様書 §1-3)。\n *\n * @param email - Raw address, possibly absent. 生のアドレス (未設定もあり得る)。\n * @returns The comparison form; empty string when there is nothing to compare.\n * 照合形。照合対象が無い場合は空文字。\n */\nexport const normalizeAuthEmail = (email: string | null | undefined): string => (email ?? \"\").trim().toLowerCase()\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(normalizeAuthEmail).filter(Boolean))\n // ドメインは \"example.com\" と \"@example.com\" の双方を受け付ける。管理 UI は後者の形式を案内し、\n // 環境変数は前者で書かれているため、正規化しないと UI から登録したドメインが一切一致しない。\n const domains = new Set(allowedDomains.map((d) => normalizeAuthEmail(d).replace(/^@+/, \"\")).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 = normalizeAuthEmail(email)\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,uBAAAC,EAAA,sBAAAC,EAAA,wBAAAC,IAAA,eAAAC,EAAAP,GCuBO,IAAMQ,EAAsBC,IAA8CA,GAAS,IAAI,KAAK,EAAE,YAAY,EASpGC,EAAuB,CAACC,EAAmC,CAAC,EAAGC,EAAoC,CAAC,IAAuD,CACpK,IAAMC,EAAS,IAAI,IAAIF,EAAc,IAAIH,CAAkB,EAAE,OAAO,OAAO,CAAC,EAGtEM,EAAU,IAAI,IAAIF,EAAe,IAAKG,GAAMP,EAAmBO,CAAC,EAAE,QAAQ,MAAO,EAAE,CAAC,EAAE,OAAO,OAAO,CAAC,EAC3G,GAAIF,EAAO,OAAS,GAAKC,EAAQ,OAAS,EACtC,MAAM,IAAI,MAAM,6DAA6D,EAEjF,OAAQL,GAA8C,CAClD,IAAMO,EAAaR,EAAmBC,CAAK,EAC3C,GAAIO,IAAe,GAAI,MAAO,GAC9B,GAAIH,EAAO,IAAIG,CAAU,EAAG,MAAO,GAEnC,IAAMC,EAAKD,EAAW,YAAY,GAAG,EACrC,OAAOC,GAAM,GAAKH,EAAQ,IAAIE,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,EChCf,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","normalizeAuthEmail","parseAllowlistCsv","resolveAuthMessages","__toCommonJS","normalizeAuthEmail","email","emailDomainAllowlist","allowedEmails","allowedDomains","emails","domains","d","normalized","at","parseAllowlistCsv","raw","s","defaultAuthMessages","resolveAuthMessages","over"]}
package/dist/index.mjs CHANGED
@@ -1,2 +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};
1
+ var o=e=>(e??"").trim().toLowerCase(),d=(e=[],n=[])=>{let r=new Set(e.map(o).filter(Boolean)),l=new Set(n.map(s=>o(s).replace(/^@+/,"")).filter(Boolean));if(r.size===0&&l.size===0)throw new Error("At least one of allowedEmails or allowedDomains must be set");return s=>{let t=o(s);if(t==="")return!1;if(r.has(t))return!0;let i=t.lastIndexOf("@");return i>=0&&l.has(t.slice(i+1))}},u=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"},m=e=>e?{...a,...e}:a;export{a as defaultAuthMessages,d as emailDomainAllowlist,o as normalizeAuthEmail,u as parseAllowlistCsv,m as resolveAuthMessages};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1 +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"]}
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 * Normalizes an address for comparison: trims surrounding whitespace and lowercases.\n * 照合用にアドレスを正規化する処理。前後の空白を除去し小文字化する。\n *\n * **この 1 本が照合形の唯一の出所である。** 許可リスト・外部ディレクトリ台帳・所属の突合は\n * すべてこれを通すこと。片側だけが別の正規化を持つと、台帳には載っているのに許可されない\n * (あるいはその逆の) 食い違いが、どこにもエラーを出さないまま成立する。\n *\n * Python 側の姉妹実装 `access_policy.py` と同一セマンティクスであることが契約 (設計仕様書 §1-3)。\n *\n * @param email - Raw address, possibly absent. 生のアドレス (未設定もあり得る)。\n * @returns The comparison form; empty string when there is nothing to compare.\n * 照合形。照合対象が無い場合は空文字。\n */\nexport const normalizeAuthEmail = (email: string | null | undefined): string => (email ?? \"\").trim().toLowerCase()\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(normalizeAuthEmail).filter(Boolean))\n // ドメインは \"example.com\" と \"@example.com\" の双方を受け付ける。管理 UI は後者の形式を案内し、\n // 環境変数は前者で書かれているため、正規化しないと UI から登録したドメインが一切一致しない。\n const domains = new Set(allowedDomains.map((d) => normalizeAuthEmail(d).replace(/^@+/, \"\")).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 = normalizeAuthEmail(email)\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":"AAuBO,IAAMA,EAAsBC,IAA8CA,GAAS,IAAI,KAAK,EAAE,YAAY,EASpGC,EAAuB,CAACC,EAAmC,CAAC,EAAGC,EAAoC,CAAC,IAAuD,CACpK,IAAMC,EAAS,IAAI,IAAIF,EAAc,IAAIH,CAAkB,EAAE,OAAO,OAAO,CAAC,EAGtEM,EAAU,IAAI,IAAIF,EAAe,IAAKG,GAAMP,EAAmBO,CAAC,EAAE,QAAQ,MAAO,EAAE,CAAC,EAAE,OAAO,OAAO,CAAC,EAC3G,GAAIF,EAAO,OAAS,GAAKC,EAAQ,OAAS,EACtC,MAAM,IAAI,MAAM,6DAA6D,EAEjF,OAAQL,GAA8C,CAClD,IAAMO,EAAaR,EAAmBC,CAAK,EAC3C,GAAIO,IAAe,GAAI,MAAO,GAC9B,GAAIH,EAAO,IAAIG,CAAU,EAAG,MAAO,GAEnC,IAAMC,EAAKD,EAAW,YAAY,GAAG,EACrC,OAAOC,GAAM,GAAKH,EAAQ,IAAIE,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,EChCf,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":["normalizeAuthEmail","email","emailDomainAllowlist","allowedEmails","allowedDomains","emails","domains","d","normalized","at","parseAllowlistCsv","raw","s","defaultAuthMessages","resolveAuthMessages","over"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiquants/auth-core",
3
- "version": "0.1.1",
3
+ "version": "0.4.0",
4
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
5
  "sideEffects": false,
6
6
  "main": "dist/index.js",