@aiquants/auth-react-router 0.15.0 → 0.16.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 +100 -7
- package/dist/admin.d.mts +13 -436
- package/dist/admin.d.ts +13 -436
- package/dist/admin.js +1 -2
- package/dist/admin.mjs +1 -2
- package/dist/client.d.mts +1 -10
- package/dist/client.d.ts +1 -10
- package/dist/client.js +0 -1
- package/dist/client.mjs +0 -1
- package/dist/server.d.mts +6 -146
- package/dist/server.d.ts +6 -146
- package/dist/server.js +0 -1
- package/dist/server.mjs +0 -1
- package/package.json +12 -16
- package/dist/admin.js.map +0 -1
- package/dist/admin.mjs.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/client.mjs.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/server.mjs.map +0 -1
- package/src/admin/app.ts +0 -9
- package/src/admin/format.ts +0 -58
- package/src/admin/labels.ts +0 -838
- package/src/admin/server.ts +0 -1143
- package/src/admin/ui.tsx +0 -276
- package/src/admin/views.tsx +0 -2120
- package/src/admin.ts +0 -5
- package/src/client/GoogleForm.tsx +0 -47
- package/src/client.ts +0 -6
- package/src/server/auth-server.ts +0 -69
- package/src/server/authenticate-in-loader.ts +0 -243
- package/src/server/authenticate.ts +0 -224
- package/src/server/google-strategy-impl.ts +0 -154
- package/src/server/google-strategy.ts +0 -183
- package/src/server/handlers.ts +0 -139
- package/src/server/logger.ts +0 -14
- package/src/server/ports.ts +0 -113
- package/src/server/session.ts +0 -132
- package/src/server.ts +0 -14
- package/src/styles/standalone.entry.css +0 -6
package/dist/client.d.ts
CHANGED
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
import * as React from
|
|
2
|
-
|
|
3
|
-
/** GoogleForm のプロパティ。 */
|
|
1
|
+
import * as React from "react";
|
|
4
2
|
type GoogleFormProps = {
|
|
5
|
-
/** フォーム action の値 (loginAction の switch と一致させる)。既定 "Sign In with Google"。 */
|
|
6
3
|
actionValue?: string;
|
|
7
|
-
/** ボタンの表示ラベル。既定 "Sign in with Google"。 */
|
|
8
4
|
label?: string;
|
|
9
5
|
};
|
|
10
|
-
/**
|
|
11
|
-
* Renders the Google sign-in form (POST with `_action`).
|
|
12
|
-
* Google サインインフォーム (`_action` 付き POST) を描画する。
|
|
13
|
-
*/
|
|
14
6
|
declare const GoogleForm: ({ actionValue, label }?: GoogleFormProps) => React.JSX.Element;
|
|
15
|
-
|
|
16
7
|
export { GoogleForm, type GoogleFormProps };
|
package/dist/client.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
1
|
"use strict";var i=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var u=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var f=(e,o)=>{for(var t in o)i(e,t,{get:o[t],enumerable:!0})},d=(e,o,t,l)=>{if(o&&typeof o=="object"||typeof o=="function")for(let r of u(o))!b.call(e,r)&&r!==t&&i(e,r,{get:()=>o[r],enumerable:!(l=p(o,r))||l.enumerable});return e};var F=e=>d(i({},"__esModule",{value:!0}),e);var h={};f(h,{GoogleForm:()=>a});module.exports=F(h);var s=require("react-icons/fa"),m=require("react-router"),g=require("tailwind-variants"),n=require("react/jsx-runtime"),x=(0,g.tv)({slots:{googleForm:"my-4",btn:"w-full flex items-center justify-center gap-3 rounded-2xl bg-gradient-to-r from-blue-500 to-indigo-500 px-6 py-3 text-white font-bold shadow-lg transition-all duration-300 hover:from-blue-600 hover:to-indigo-600 hover:scale-105 focus:outline-none focus:ring-2 focus:ring-blue-300",icon:"",btnText:"text-base tracking-wide"}}),a=({actionValue:e="Sign In with Google",label:o="Sign in with Google"}={})=>{let{googleForm:t,btn:l,icon:r,btnText:c}=x();return(0,n.jsx)(m.Form,{method:"POST",className:t(),children:(0,n.jsxs)("button",{type:"submit",name:"_action",value:e,className:l(),children:[(0,n.jsx)(s.FaGoogle,{size:24,className:"text-white drop-shadow"}),(0,n.jsx)("span",{className:c(),children:o})]})})};0&&(module.exports={GoogleForm});
|
|
2
|
-
//# sourceMappingURL=client.js.map
|
package/dist/client.mjs
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
1
|
import{FaGoogle as i}from"react-icons/fa";import{Form as s}from"react-router";import{tv as m}from"tailwind-variants";import{jsx as o,jsxs as c}from"react/jsx-runtime";var g=m({slots:{googleForm:"my-4",btn:"w-full flex items-center justify-center gap-3 rounded-2xl bg-gradient-to-r from-blue-500 to-indigo-500 px-6 py-3 text-white font-bold shadow-lg transition-all duration-300 hover:from-blue-600 hover:to-indigo-600 hover:scale-105 focus:outline-none focus:ring-2 focus:ring-blue-300",icon:"",btnText:"text-base tracking-wide"}}),a=({actionValue:e="Sign In with Google",label:t="Sign in with Google"}={})=>{let{googleForm:r,btn:n,icon:p,btnText:l}=g();return o(s,{method:"POST",className:r(),children:c("button",{type:"submit",name:"_action",value:e,className:n(),children:[o(i,{size:24,className:"text-white drop-shadow"}),o("span",{className:l(),children:t})]})})};export{a as GoogleForm};
|
|
2
|
-
//# sourceMappingURL=client.mjs.map
|
package/dist/server.d.mts
CHANGED
|
@@ -1,122 +1,56 @@
|
|
|
1
|
-
import * as react_router from
|
|
2
|
-
import { Session } from
|
|
3
|
-
import * as remix_auth from
|
|
4
|
-
import { Authenticator } from
|
|
5
|
-
import * as _aiquants_auth_core from
|
|
6
|
-
import { GoogleProfileBase, SessionProfile, AuthMessages, MyGoogleProfile } from
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @module ports
|
|
10
|
-
* @description DI port types for the React Router auth adapter.
|
|
11
|
-
* React Router 認証アダプタの DI ポート型定義。
|
|
12
|
-
*
|
|
13
|
-
* アプリ固有の結合 (env・backend HTTP・DB 由来の写真解決・DB ウォームアップ・テストバックドア) は
|
|
14
|
-
* すべてここで定義するポートとして注入する。パッケージ内には汎用ロジックだけを置く。
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
/** OAuth Google の資格情報とオプション (scopes はアプリ注入 — sanledge が拡張)。 */
|
|
1
|
+
import * as react_router from "react-router";
|
|
2
|
+
import { Session } from "react-router";
|
|
3
|
+
import * as remix_auth from "remix-auth";
|
|
4
|
+
import { Authenticator } from "remix-auth";
|
|
5
|
+
import * as _aiquants_auth_core from "@aiquants/auth-core";
|
|
6
|
+
import { GoogleProfileBase, SessionProfile, AuthMessages, MyGoogleProfile } from "@aiquants/auth-core";
|
|
18
7
|
type GoogleOAuthConfig = {
|
|
19
8
|
clientId: string;
|
|
20
9
|
clientSecret: string;
|
|
21
10
|
redirectURI: string;
|
|
22
|
-
/** 追加スコープ (既定は openid profile email)。 */
|
|
23
11
|
scopes?: string[];
|
|
24
12
|
accessType?: "online" | "offline";
|
|
25
13
|
includeGrantedScopes?: boolean;
|
|
26
14
|
prompt?: "none" | "consent" | "select_account";
|
|
27
15
|
};
|
|
28
|
-
/** セッション cookie 設定 (既定は __session/lax/30 日)。secrets のみ必須。 */
|
|
29
16
|
type SessionConfig = {
|
|
30
17
|
secrets: string[];
|
|
31
|
-
/** cookie 名。既定 "__session" (変更すると全ライブセッションが無効化されるため既定を推奨)。 */
|
|
32
18
|
cookieName?: string;
|
|
33
|
-
/** 有効期間 (秒)。既定 60*60*24*30。 */
|
|
34
19
|
maxAge?: number;
|
|
35
|
-
/** Secure 属性。既定は NODE_ENV === "production"。 */
|
|
36
20
|
secure?: boolean;
|
|
37
|
-
/** セッション内のユーザーキー。既定 "user"。 */
|
|
38
21
|
sessionKey?: string;
|
|
39
22
|
};
|
|
40
|
-
/** バックエンド (FastAPI) 認証ポート。verify → allowlist → signup の順で呼ばれる。 */
|
|
41
23
|
type BackendAuthPort = {
|
|
42
|
-
/** GET /openid/verify 相当。ok なら true。 */
|
|
43
24
|
verify: (provider: string, accessToken: string) => Promise<boolean>;
|
|
44
|
-
/**
|
|
45
|
-
* POST /openid/signup?overwrite_existing=true 相当 (毎ログイン実行)。
|
|
46
|
-
*
|
|
47
|
-
* `profile` を渡すのは、サインアップ直後にホスト側が identity を必要とするためである。
|
|
48
|
-
* この呼び出しでユーザー行が作られるので、直後に「この人物は何に属するか」を解決したい
|
|
49
|
-
* ホストは、ここでプロフィールを受け取れないとトークンから引き直すしかない。
|
|
50
|
-
*/
|
|
51
25
|
signup: (provider: string, accessToken: string, profile: GoogleProfileBase) => Promise<unknown>;
|
|
52
26
|
};
|
|
53
|
-
/** ユーザー写真解決ポート (DB 参照等の実装はアプリ側)。キャッシュ + dedup はパッケージ内。 */
|
|
54
27
|
type UserPhotoPort = (openid: string) => Promise<string>;
|
|
55
|
-
/** 毎リクエストの DB 接続ウォームアップ (quants は 2 プール、sanledge は 1 プール)。 */
|
|
56
28
|
type WarmupPort = () => Promise<void>;
|
|
57
|
-
/** テストバックドア (quants は mock_user_id パース + 名前マップ、sanledge は固定 id)。 */
|
|
58
29
|
type MockUserPort = {
|
|
59
|
-
/** このリクエストでバックドアを使うか (NODE_ENV/cookie の判定はアプリ側)。 */
|
|
60
30
|
enabled: (request: Request) => boolean;
|
|
61
|
-
/** バックドア時に返すセッションプロファイル。 */
|
|
62
31
|
resolve: (request: Request) => SessionProfile;
|
|
63
32
|
};
|
|
64
|
-
/** ロガーポート (既定は console)。 */
|
|
65
33
|
type LoggerPort = {
|
|
66
34
|
info: (...args: unknown[]) => void;
|
|
67
35
|
warn: (...args: unknown[]) => void;
|
|
68
36
|
error: (...args: unknown[]) => void;
|
|
69
37
|
};
|
|
70
|
-
/** 認証サーバーの集約設定。 */
|
|
71
38
|
type AuthServerConfig = {
|
|
72
39
|
google: GoogleOAuthConfig;
|
|
73
40
|
session: SessionConfig;
|
|
74
|
-
/**
|
|
75
|
-
* allowlist 判定 (auth-core の `emailDomainAllowlist` を推奨)。
|
|
76
|
-
*
|
|
77
|
-
* 非同期を許すのは、環境変数に加えてデータベース等の可変な供給元を参照する構成のため。
|
|
78
|
-
* ⚠️ 呼び出し側は必ず `await` すること。`Promise` は常に truthy なので `if (!fn(email))`
|
|
79
|
-
* のまま非同期実装を渡すと **判定が素通りして fail-open** になる (型検査では検出できない)。
|
|
80
|
-
*/
|
|
81
41
|
isEmailAllowed: (email: string | null | undefined) => boolean | Promise<boolean>;
|
|
82
|
-
/**
|
|
83
|
-
* Account-status port: return `false` ONLY for a known-deactivated account.
|
|
84
|
-
* アカウント状態ポート。**無効化済みと判明した場合のみ** `false` を返す。
|
|
85
|
-
*
|
|
86
|
-
* allowlist が「そのメールアドレスがこのアプリを使ってよいか」を判定するのに対し、こちらは
|
|
87
|
-
* 「そのアカウントが現在有効か」を判定する。ログイン時と **毎リクエストのセッション解決時** の
|
|
88
|
-
* 双方で参照されるため、無効化は既存セッションに対しても即座に効く。
|
|
89
|
-
*
|
|
90
|
-
* 未登録ユーザー (初回ログインでこれから作成される) では `true` を返すこと。`false` を返すと
|
|
91
|
-
* サインアップ経路ごと塞いでしまう。省略時は常に有効として扱う (既存構成の挙動を変えない)。
|
|
92
|
-
*/
|
|
93
42
|
isUserActive?: (profile: GoogleProfileBase) => boolean | Promise<boolean>;
|
|
94
|
-
/** バックエンド verify/signup。省略時はスキップ (フロント単独運用)。 */
|
|
95
43
|
backendAuth?: BackendAuthPort;
|
|
96
|
-
/** ユーザー写真解決。省略時は常に空文字。 */
|
|
97
44
|
getUserPhoto?: UserPhotoPort;
|
|
98
|
-
/** 毎リクエストのウォームアップ。省略時は何もしない。 */
|
|
99
45
|
warmup?: WarmupPort;
|
|
100
|
-
/** テストバックドア。省略時は無効 (本番安全側)。 */
|
|
101
46
|
mockUser?: MockUserPort;
|
|
102
|
-
/** ログインページのパス。既定 "/auth/login"。 */
|
|
103
47
|
loginPath?: string;
|
|
104
|
-
/** ログイン成功後のリダイレクト先。既定 "/"。 */
|
|
105
48
|
postLoginRedirect?: string;
|
|
106
|
-
/** ログアウト後のリダイレクト先。既定 "/"。 */
|
|
107
49
|
postLogoutRedirect?: string;
|
|
108
|
-
/** エラーメッセージ上書き (既定は日本語の原本文言)。 */
|
|
109
50
|
messages?: Partial<AuthMessages>;
|
|
110
|
-
/** ロガー (既定 console)。 */
|
|
111
51
|
logger?: LoggerPort;
|
|
112
52
|
};
|
|
113
|
-
|
|
114
|
-
/** createAuthServer が返す認証サーバー束。 */
|
|
115
53
|
type AuthServer = ReturnType<typeof createAuthServer>;
|
|
116
|
-
/**
|
|
117
|
-
* Builds the full auth server bundle from a single config.
|
|
118
|
-
* 単一の設定から認証サーバー束一式を生成する。
|
|
119
|
-
*/
|
|
120
54
|
declare const createAuthServer: (config: AuthServerConfig) => {
|
|
121
55
|
loginLoader: ({ request }: {
|
|
122
56
|
request: Request;
|
|
@@ -196,15 +130,7 @@ declare const createAuthServer: (config: AuthServerConfig) => {
|
|
|
196
130
|
}>;
|
|
197
131
|
};
|
|
198
132
|
};
|
|
199
|
-
|
|
200
|
-
/** createAuthSessionStorage が返すセッション操作束。 */
|
|
201
133
|
type AuthSessionStorage = ReturnType<typeof createAuthSessionStorage>;
|
|
202
|
-
/**
|
|
203
|
-
* Builds the cookie session storage and session helpers.
|
|
204
|
-
* cookie セッションストレージとセッションヘルパー群を生成する。
|
|
205
|
-
*
|
|
206
|
-
* @throws secrets が空の場合 (fail-closed の設定ガード)。
|
|
207
|
-
*/
|
|
208
134
|
declare const createAuthSessionStorage: (config: SessionConfig, options?: {
|
|
209
135
|
mockUser?: MockUserPort;
|
|
210
136
|
loginPath?: string;
|
|
@@ -222,34 +148,17 @@ declare const createAuthSessionStorage: (config: SessionConfig, options?: {
|
|
|
222
148
|
commitSession: (session: react_router.Session<react_router.SessionData, react_router.SessionData>, options?: react_router.CookieSerializeOptions) => Promise<string>;
|
|
223
149
|
destroySession: (session: react_router.Session<react_router.SessionData, react_router.SessionData>, options?: react_router.CookieSerializeOptions) => Promise<string>;
|
|
224
150
|
};
|
|
225
|
-
|
|
226
|
-
/** トークンリフレッシュ失敗の基底エラー。 */
|
|
227
151
|
declare class TokenRefreshError extends Error {
|
|
228
152
|
constructor(message: string);
|
|
229
153
|
}
|
|
230
|
-
/** Google の invalid_grant (失効/取り消し済みリフレッシュトークン) エラー。 */
|
|
231
154
|
declare class GoogleInvalidGrantError extends TokenRefreshError {
|
|
232
155
|
constructor();
|
|
233
156
|
}
|
|
234
|
-
/**
|
|
235
|
-
* Signals that authentication is required when the caller opted out of redirecting.
|
|
236
|
-
* 呼び出し側がリダイレクトを拒否した (`failureRedirect: null`) 際に認証要求を伝えるエラー。
|
|
237
|
-
*
|
|
238
|
-
* `redirect()` を投げてしまうと React Router がそれを本物の 302 として扱うため、
|
|
239
|
-
* 「リダイレクトしない」という契約を型で表現できるこのエラーへ置き換える。
|
|
240
|
-
* セッション破棄が発生した場合はその `Set-Cookie` を `setCookie` で運ぶ
|
|
241
|
-
* (捨てると無効セッションがブラウザに残り続ける)。
|
|
242
|
-
*/
|
|
243
157
|
declare class AuthRequiredError extends Error {
|
|
244
158
|
readonly setCookie?: string;
|
|
245
159
|
constructor(setCookie?: string);
|
|
246
160
|
}
|
|
247
|
-
/** createAuthenticate が返す認証関数束。 */
|
|
248
161
|
type AuthenticateFns = ReturnType<typeof createAuthenticate>;
|
|
249
|
-
/**
|
|
250
|
-
* Builds the `authenticate` / `refreshedAccessToken` pair wired to injected config.
|
|
251
|
-
* 注入設定に結線した `authenticate` / `refreshedAccessToken` を生成する。
|
|
252
|
-
*/
|
|
253
162
|
declare const createAuthenticate: (config: AuthServerConfig, storage: AuthSessionStorage, logger: LoggerPort) => {
|
|
254
163
|
authenticate: (request: Request, session: Session, options?: {
|
|
255
164
|
failureRedirect?: string | null;
|
|
@@ -264,22 +173,6 @@ declare const createAuthenticate: (config: AuthServerConfig, storage: AuthSessio
|
|
|
264
173
|
expirationDateMs: number;
|
|
265
174
|
}>;
|
|
266
175
|
};
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* @module authenticate-in-loader
|
|
270
|
-
* @description Per-request loader authentication guard with photo cache and warmup.
|
|
271
|
-
* 写真キャッシュとウォームアップ付きの毎リクエストローダー認証ガード。
|
|
272
|
-
*
|
|
273
|
-
* 原本 (quants-react-router authenticateInLoader.server.ts) を挙動保存で移植。
|
|
274
|
-
* DB クエリ (getOpenids) は `getUserPhoto` ポートへ、DB プールウォームアップは `warmup`
|
|
275
|
-
* ポートへ置き換えた。写真キャッシュ (Map + pending dedup) はファクトリインスタンスごとに
|
|
276
|
-
* 保持する (module-level mutable state の排除)。
|
|
277
|
-
*/
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Builds the per-request loader guard wired to injected ports.
|
|
281
|
-
* 注入ポートに結線した毎リクエストローダーガードを生成する。
|
|
282
|
-
*/
|
|
283
176
|
declare const createAuthenticateInLoader: (config: AuthServerConfig, storage: AuthSessionStorage, authFns: AuthenticateFns, logger: LoggerPort) => {
|
|
284
177
|
(request: Request, options: {
|
|
285
178
|
failureRedirect: string;
|
|
@@ -294,39 +187,7 @@ declare const createAuthenticateInLoader: (config: AuthServerConfig, storage: Au
|
|
|
294
187
|
cookie?: string;
|
|
295
188
|
}>;
|
|
296
189
|
};
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* @module google-strategy
|
|
300
|
-
* @description Google OAuth authenticator factory (remix-auth v4 + local GoogleStrategy).
|
|
301
|
-
* Google OAuth 認証器ファクトリ (remix-auth v4 + 同梱の GoogleStrategy)。
|
|
302
|
-
*
|
|
303
|
-
* 原本 (quants-react-router auth.server.ts) の verify コールバックを挙動保存で移植。
|
|
304
|
-
* env 読取り・backend HTTP・allowlist・エラーメッセージはすべてポート/設定注入に置き換えた。
|
|
305
|
-
* 検証順序 (Google プロフィール検証 → backend verify → allowlist → backend signup)、
|
|
306
|
-
* リダイレクト先 (`<loginPath>?error=<encoded>`)、エラーメッセージ文言は原本と等価。
|
|
307
|
-
*/
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Builds a remix-auth Authenticator with the Google strategy wired to injected ports.
|
|
311
|
-
* 注入ポートに結線した Google ストラテジー付き Authenticator を生成する。
|
|
312
|
-
*/
|
|
313
190
|
declare const createGoogleAuthenticator: (config: AuthServerConfig) => Authenticator<MyGoogleProfile>;
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* @module handlers
|
|
317
|
-
* @description Route handler factories for login / logout / Google callback.
|
|
318
|
-
* login / logout / Google コールバックのルートハンドラ工場。
|
|
319
|
-
*
|
|
320
|
-
* 原本 (quants-react-router routes/_auth+ の 4 ルート + features/logoutAndRedirect.ts) の
|
|
321
|
-
* サーバー側ロジックを挙動保存で移植。UI (JSX) はアプリ側に残す。
|
|
322
|
-
* コールバックのセッション保存形状 (SessionProfile、photo/_json/photos を含まない) は
|
|
323
|
-
* cookie byte 互換のため厳密に保存する。
|
|
324
|
-
*/
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Builds loader/action handlers for the auth routes.
|
|
328
|
-
* 認証ルート用の loader/action ハンドラ群を生成する。
|
|
329
|
-
*/
|
|
330
191
|
declare const createAuthHandlers: (config: AuthServerConfig, storage: AuthSessionStorage, authenticator: Authenticator<MyGoogleProfile>, logger: LoggerPort) => {
|
|
331
192
|
loginLoader: ({ request }: {
|
|
332
193
|
request: Request;
|
|
@@ -345,5 +206,4 @@ declare const createAuthHandlers: (config: AuthServerConfig, storage: AuthSessio
|
|
|
345
206
|
request: Request;
|
|
346
207
|
}) => Promise<never>;
|
|
347
208
|
};
|
|
348
|
-
|
|
349
209
|
export { AuthRequiredError, type AuthServer, type AuthServerConfig, type AuthSessionStorage, type BackendAuthPort, GoogleInvalidGrantError, type GoogleOAuthConfig, type LoggerPort, type MockUserPort, type SessionConfig, TokenRefreshError, type UserPhotoPort, type WarmupPort, createAuthHandlers, createAuthServer, createAuthSessionStorage, createAuthenticate, createAuthenticateInLoader, createGoogleAuthenticator };
|
package/dist/server.d.ts
CHANGED
|
@@ -1,122 +1,56 @@
|
|
|
1
|
-
import * as react_router from
|
|
2
|
-
import { Session } from
|
|
3
|
-
import * as remix_auth from
|
|
4
|
-
import { Authenticator } from
|
|
5
|
-
import * as _aiquants_auth_core from
|
|
6
|
-
import { GoogleProfileBase, SessionProfile, AuthMessages, MyGoogleProfile } from
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @module ports
|
|
10
|
-
* @description DI port types for the React Router auth adapter.
|
|
11
|
-
* React Router 認証アダプタの DI ポート型定義。
|
|
12
|
-
*
|
|
13
|
-
* アプリ固有の結合 (env・backend HTTP・DB 由来の写真解決・DB ウォームアップ・テストバックドア) は
|
|
14
|
-
* すべてここで定義するポートとして注入する。パッケージ内には汎用ロジックだけを置く。
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
/** OAuth Google の資格情報とオプション (scopes はアプリ注入 — sanledge が拡張)。 */
|
|
1
|
+
import * as react_router from "react-router";
|
|
2
|
+
import { Session } from "react-router";
|
|
3
|
+
import * as remix_auth from "remix-auth";
|
|
4
|
+
import { Authenticator } from "remix-auth";
|
|
5
|
+
import * as _aiquants_auth_core from "@aiquants/auth-core";
|
|
6
|
+
import { GoogleProfileBase, SessionProfile, AuthMessages, MyGoogleProfile } from "@aiquants/auth-core";
|
|
18
7
|
type GoogleOAuthConfig = {
|
|
19
8
|
clientId: string;
|
|
20
9
|
clientSecret: string;
|
|
21
10
|
redirectURI: string;
|
|
22
|
-
/** 追加スコープ (既定は openid profile email)。 */
|
|
23
11
|
scopes?: string[];
|
|
24
12
|
accessType?: "online" | "offline";
|
|
25
13
|
includeGrantedScopes?: boolean;
|
|
26
14
|
prompt?: "none" | "consent" | "select_account";
|
|
27
15
|
};
|
|
28
|
-
/** セッション cookie 設定 (既定は __session/lax/30 日)。secrets のみ必須。 */
|
|
29
16
|
type SessionConfig = {
|
|
30
17
|
secrets: string[];
|
|
31
|
-
/** cookie 名。既定 "__session" (変更すると全ライブセッションが無効化されるため既定を推奨)。 */
|
|
32
18
|
cookieName?: string;
|
|
33
|
-
/** 有効期間 (秒)。既定 60*60*24*30。 */
|
|
34
19
|
maxAge?: number;
|
|
35
|
-
/** Secure 属性。既定は NODE_ENV === "production"。 */
|
|
36
20
|
secure?: boolean;
|
|
37
|
-
/** セッション内のユーザーキー。既定 "user"。 */
|
|
38
21
|
sessionKey?: string;
|
|
39
22
|
};
|
|
40
|
-
/** バックエンド (FastAPI) 認証ポート。verify → allowlist → signup の順で呼ばれる。 */
|
|
41
23
|
type BackendAuthPort = {
|
|
42
|
-
/** GET /openid/verify 相当。ok なら true。 */
|
|
43
24
|
verify: (provider: string, accessToken: string) => Promise<boolean>;
|
|
44
|
-
/**
|
|
45
|
-
* POST /openid/signup?overwrite_existing=true 相当 (毎ログイン実行)。
|
|
46
|
-
*
|
|
47
|
-
* `profile` を渡すのは、サインアップ直後にホスト側が identity を必要とするためである。
|
|
48
|
-
* この呼び出しでユーザー行が作られるので、直後に「この人物は何に属するか」を解決したい
|
|
49
|
-
* ホストは、ここでプロフィールを受け取れないとトークンから引き直すしかない。
|
|
50
|
-
*/
|
|
51
25
|
signup: (provider: string, accessToken: string, profile: GoogleProfileBase) => Promise<unknown>;
|
|
52
26
|
};
|
|
53
|
-
/** ユーザー写真解決ポート (DB 参照等の実装はアプリ側)。キャッシュ + dedup はパッケージ内。 */
|
|
54
27
|
type UserPhotoPort = (openid: string) => Promise<string>;
|
|
55
|
-
/** 毎リクエストの DB 接続ウォームアップ (quants は 2 プール、sanledge は 1 プール)。 */
|
|
56
28
|
type WarmupPort = () => Promise<void>;
|
|
57
|
-
/** テストバックドア (quants は mock_user_id パース + 名前マップ、sanledge は固定 id)。 */
|
|
58
29
|
type MockUserPort = {
|
|
59
|
-
/** このリクエストでバックドアを使うか (NODE_ENV/cookie の判定はアプリ側)。 */
|
|
60
30
|
enabled: (request: Request) => boolean;
|
|
61
|
-
/** バックドア時に返すセッションプロファイル。 */
|
|
62
31
|
resolve: (request: Request) => SessionProfile;
|
|
63
32
|
};
|
|
64
|
-
/** ロガーポート (既定は console)。 */
|
|
65
33
|
type LoggerPort = {
|
|
66
34
|
info: (...args: unknown[]) => void;
|
|
67
35
|
warn: (...args: unknown[]) => void;
|
|
68
36
|
error: (...args: unknown[]) => void;
|
|
69
37
|
};
|
|
70
|
-
/** 認証サーバーの集約設定。 */
|
|
71
38
|
type AuthServerConfig = {
|
|
72
39
|
google: GoogleOAuthConfig;
|
|
73
40
|
session: SessionConfig;
|
|
74
|
-
/**
|
|
75
|
-
* allowlist 判定 (auth-core の `emailDomainAllowlist` を推奨)。
|
|
76
|
-
*
|
|
77
|
-
* 非同期を許すのは、環境変数に加えてデータベース等の可変な供給元を参照する構成のため。
|
|
78
|
-
* ⚠️ 呼び出し側は必ず `await` すること。`Promise` は常に truthy なので `if (!fn(email))`
|
|
79
|
-
* のまま非同期実装を渡すと **判定が素通りして fail-open** になる (型検査では検出できない)。
|
|
80
|
-
*/
|
|
81
41
|
isEmailAllowed: (email: string | null | undefined) => boolean | Promise<boolean>;
|
|
82
|
-
/**
|
|
83
|
-
* Account-status port: return `false` ONLY for a known-deactivated account.
|
|
84
|
-
* アカウント状態ポート。**無効化済みと判明した場合のみ** `false` を返す。
|
|
85
|
-
*
|
|
86
|
-
* allowlist が「そのメールアドレスがこのアプリを使ってよいか」を判定するのに対し、こちらは
|
|
87
|
-
* 「そのアカウントが現在有効か」を判定する。ログイン時と **毎リクエストのセッション解決時** の
|
|
88
|
-
* 双方で参照されるため、無効化は既存セッションに対しても即座に効く。
|
|
89
|
-
*
|
|
90
|
-
* 未登録ユーザー (初回ログインでこれから作成される) では `true` を返すこと。`false` を返すと
|
|
91
|
-
* サインアップ経路ごと塞いでしまう。省略時は常に有効として扱う (既存構成の挙動を変えない)。
|
|
92
|
-
*/
|
|
93
42
|
isUserActive?: (profile: GoogleProfileBase) => boolean | Promise<boolean>;
|
|
94
|
-
/** バックエンド verify/signup。省略時はスキップ (フロント単独運用)。 */
|
|
95
43
|
backendAuth?: BackendAuthPort;
|
|
96
|
-
/** ユーザー写真解決。省略時は常に空文字。 */
|
|
97
44
|
getUserPhoto?: UserPhotoPort;
|
|
98
|
-
/** 毎リクエストのウォームアップ。省略時は何もしない。 */
|
|
99
45
|
warmup?: WarmupPort;
|
|
100
|
-
/** テストバックドア。省略時は無効 (本番安全側)。 */
|
|
101
46
|
mockUser?: MockUserPort;
|
|
102
|
-
/** ログインページのパス。既定 "/auth/login"。 */
|
|
103
47
|
loginPath?: string;
|
|
104
|
-
/** ログイン成功後のリダイレクト先。既定 "/"。 */
|
|
105
48
|
postLoginRedirect?: string;
|
|
106
|
-
/** ログアウト後のリダイレクト先。既定 "/"。 */
|
|
107
49
|
postLogoutRedirect?: string;
|
|
108
|
-
/** エラーメッセージ上書き (既定は日本語の原本文言)。 */
|
|
109
50
|
messages?: Partial<AuthMessages>;
|
|
110
|
-
/** ロガー (既定 console)。 */
|
|
111
51
|
logger?: LoggerPort;
|
|
112
52
|
};
|
|
113
|
-
|
|
114
|
-
/** createAuthServer が返す認証サーバー束。 */
|
|
115
53
|
type AuthServer = ReturnType<typeof createAuthServer>;
|
|
116
|
-
/**
|
|
117
|
-
* Builds the full auth server bundle from a single config.
|
|
118
|
-
* 単一の設定から認証サーバー束一式を生成する。
|
|
119
|
-
*/
|
|
120
54
|
declare const createAuthServer: (config: AuthServerConfig) => {
|
|
121
55
|
loginLoader: ({ request }: {
|
|
122
56
|
request: Request;
|
|
@@ -196,15 +130,7 @@ declare const createAuthServer: (config: AuthServerConfig) => {
|
|
|
196
130
|
}>;
|
|
197
131
|
};
|
|
198
132
|
};
|
|
199
|
-
|
|
200
|
-
/** createAuthSessionStorage が返すセッション操作束。 */
|
|
201
133
|
type AuthSessionStorage = ReturnType<typeof createAuthSessionStorage>;
|
|
202
|
-
/**
|
|
203
|
-
* Builds the cookie session storage and session helpers.
|
|
204
|
-
* cookie セッションストレージとセッションヘルパー群を生成する。
|
|
205
|
-
*
|
|
206
|
-
* @throws secrets が空の場合 (fail-closed の設定ガード)。
|
|
207
|
-
*/
|
|
208
134
|
declare const createAuthSessionStorage: (config: SessionConfig, options?: {
|
|
209
135
|
mockUser?: MockUserPort;
|
|
210
136
|
loginPath?: string;
|
|
@@ -222,34 +148,17 @@ declare const createAuthSessionStorage: (config: SessionConfig, options?: {
|
|
|
222
148
|
commitSession: (session: react_router.Session<react_router.SessionData, react_router.SessionData>, options?: react_router.CookieSerializeOptions) => Promise<string>;
|
|
223
149
|
destroySession: (session: react_router.Session<react_router.SessionData, react_router.SessionData>, options?: react_router.CookieSerializeOptions) => Promise<string>;
|
|
224
150
|
};
|
|
225
|
-
|
|
226
|
-
/** トークンリフレッシュ失敗の基底エラー。 */
|
|
227
151
|
declare class TokenRefreshError extends Error {
|
|
228
152
|
constructor(message: string);
|
|
229
153
|
}
|
|
230
|
-
/** Google の invalid_grant (失効/取り消し済みリフレッシュトークン) エラー。 */
|
|
231
154
|
declare class GoogleInvalidGrantError extends TokenRefreshError {
|
|
232
155
|
constructor();
|
|
233
156
|
}
|
|
234
|
-
/**
|
|
235
|
-
* Signals that authentication is required when the caller opted out of redirecting.
|
|
236
|
-
* 呼び出し側がリダイレクトを拒否した (`failureRedirect: null`) 際に認証要求を伝えるエラー。
|
|
237
|
-
*
|
|
238
|
-
* `redirect()` を投げてしまうと React Router がそれを本物の 302 として扱うため、
|
|
239
|
-
* 「リダイレクトしない」という契約を型で表現できるこのエラーへ置き換える。
|
|
240
|
-
* セッション破棄が発生した場合はその `Set-Cookie` を `setCookie` で運ぶ
|
|
241
|
-
* (捨てると無効セッションがブラウザに残り続ける)。
|
|
242
|
-
*/
|
|
243
157
|
declare class AuthRequiredError extends Error {
|
|
244
158
|
readonly setCookie?: string;
|
|
245
159
|
constructor(setCookie?: string);
|
|
246
160
|
}
|
|
247
|
-
/** createAuthenticate が返す認証関数束。 */
|
|
248
161
|
type AuthenticateFns = ReturnType<typeof createAuthenticate>;
|
|
249
|
-
/**
|
|
250
|
-
* Builds the `authenticate` / `refreshedAccessToken` pair wired to injected config.
|
|
251
|
-
* 注入設定に結線した `authenticate` / `refreshedAccessToken` を生成する。
|
|
252
|
-
*/
|
|
253
162
|
declare const createAuthenticate: (config: AuthServerConfig, storage: AuthSessionStorage, logger: LoggerPort) => {
|
|
254
163
|
authenticate: (request: Request, session: Session, options?: {
|
|
255
164
|
failureRedirect?: string | null;
|
|
@@ -264,22 +173,6 @@ declare const createAuthenticate: (config: AuthServerConfig, storage: AuthSessio
|
|
|
264
173
|
expirationDateMs: number;
|
|
265
174
|
}>;
|
|
266
175
|
};
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* @module authenticate-in-loader
|
|
270
|
-
* @description Per-request loader authentication guard with photo cache and warmup.
|
|
271
|
-
* 写真キャッシュとウォームアップ付きの毎リクエストローダー認証ガード。
|
|
272
|
-
*
|
|
273
|
-
* 原本 (quants-react-router authenticateInLoader.server.ts) を挙動保存で移植。
|
|
274
|
-
* DB クエリ (getOpenids) は `getUserPhoto` ポートへ、DB プールウォームアップは `warmup`
|
|
275
|
-
* ポートへ置き換えた。写真キャッシュ (Map + pending dedup) はファクトリインスタンスごとに
|
|
276
|
-
* 保持する (module-level mutable state の排除)。
|
|
277
|
-
*/
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Builds the per-request loader guard wired to injected ports.
|
|
281
|
-
* 注入ポートに結線した毎リクエストローダーガードを生成する。
|
|
282
|
-
*/
|
|
283
176
|
declare const createAuthenticateInLoader: (config: AuthServerConfig, storage: AuthSessionStorage, authFns: AuthenticateFns, logger: LoggerPort) => {
|
|
284
177
|
(request: Request, options: {
|
|
285
178
|
failureRedirect: string;
|
|
@@ -294,39 +187,7 @@ declare const createAuthenticateInLoader: (config: AuthServerConfig, storage: Au
|
|
|
294
187
|
cookie?: string;
|
|
295
188
|
}>;
|
|
296
189
|
};
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* @module google-strategy
|
|
300
|
-
* @description Google OAuth authenticator factory (remix-auth v4 + local GoogleStrategy).
|
|
301
|
-
* Google OAuth 認証器ファクトリ (remix-auth v4 + 同梱の GoogleStrategy)。
|
|
302
|
-
*
|
|
303
|
-
* 原本 (quants-react-router auth.server.ts) の verify コールバックを挙動保存で移植。
|
|
304
|
-
* env 読取り・backend HTTP・allowlist・エラーメッセージはすべてポート/設定注入に置き換えた。
|
|
305
|
-
* 検証順序 (Google プロフィール検証 → backend verify → allowlist → backend signup)、
|
|
306
|
-
* リダイレクト先 (`<loginPath>?error=<encoded>`)、エラーメッセージ文言は原本と等価。
|
|
307
|
-
*/
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Builds a remix-auth Authenticator with the Google strategy wired to injected ports.
|
|
311
|
-
* 注入ポートに結線した Google ストラテジー付き Authenticator を生成する。
|
|
312
|
-
*/
|
|
313
190
|
declare const createGoogleAuthenticator: (config: AuthServerConfig) => Authenticator<MyGoogleProfile>;
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* @module handlers
|
|
317
|
-
* @description Route handler factories for login / logout / Google callback.
|
|
318
|
-
* login / logout / Google コールバックのルートハンドラ工場。
|
|
319
|
-
*
|
|
320
|
-
* 原本 (quants-react-router routes/_auth+ の 4 ルート + features/logoutAndRedirect.ts) の
|
|
321
|
-
* サーバー側ロジックを挙動保存で移植。UI (JSX) はアプリ側に残す。
|
|
322
|
-
* コールバックのセッション保存形状 (SessionProfile、photo/_json/photos を含まない) は
|
|
323
|
-
* cookie byte 互換のため厳密に保存する。
|
|
324
|
-
*/
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Builds loader/action handlers for the auth routes.
|
|
328
|
-
* 認証ルート用の loader/action ハンドラ群を生成する。
|
|
329
|
-
*/
|
|
330
191
|
declare const createAuthHandlers: (config: AuthServerConfig, storage: AuthSessionStorage, authenticator: Authenticator<MyGoogleProfile>, logger: LoggerPort) => {
|
|
331
192
|
loginLoader: ({ request }: {
|
|
332
193
|
request: Request;
|
|
@@ -345,5 +206,4 @@ declare const createAuthHandlers: (config: AuthServerConfig, storage: AuthSessio
|
|
|
345
206
|
request: Request;
|
|
346
207
|
}) => Promise<never>;
|
|
347
208
|
};
|
|
348
|
-
|
|
349
209
|
export { AuthRequiredError, type AuthServer, type AuthServerConfig, type AuthSessionStorage, type BackendAuthPort, GoogleInvalidGrantError, type GoogleOAuthConfig, type LoggerPort, type MockUserPort, type SessionConfig, TokenRefreshError, type UserPhotoPort, type WarmupPort, createAuthHandlers, createAuthServer, createAuthSessionStorage, createAuthenticate, createAuthenticateInLoader, createGoogleAuthenticator };
|
package/dist/server.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
1
|
"use strict";var $=Object.defineProperty;var V=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var Q=Object.prototype.hasOwnProperty;var X=(e,s)=>{for(var r in s)$(e,r,{get:s[r],enumerable:!0})},Z=(e,s,r,u)=>{if(s&&typeof s=="object"||typeof s=="function")for(let i of J(s))!Q.call(e,i)&&i!==r&&$(e,i,{get:()=>s[i],enumerable:!(u=V(s,i))||u.enumerable});return e};var ee=e=>Z($({},"__esModule",{value:!0}),e);var se={};X(se,{AuthRequiredError:()=>R,GoogleInvalidGrantError:()=>G,TokenRefreshError:()=>w,createAuthHandlers:()=>O,createAuthServer:()=>Y,createAuthSessionStorage:()=>F,createAuthenticate:()=>C,createAuthenticateInLoader:()=>I,createGoogleAuthenticator:()=>D});module.exports=ee(se);var H=require("react-router"),w=class extends Error{constructor(s){super(s),this.name="TokenRefreshError"}},G=class extends w{constructor(){super("Google refresh token invalid or revoked"),this.name="GoogleInvalidGrantError"}},R=class extends Error{constructor(s){super("Authentication required"),this.name="AuthRequiredError",this.setCookie=s}},C=(e,s,r)=>{let u=e.loginPath??"/auth/login",i=s.sessionKey,m=async d=>{let g=d.get(i)?.refreshToken;if(!g)throw new Error("Refresh token not found in session");try{let a="https://www.googleapis.com/oauth2/v4/token",l={client_id:e.google.clientId,client_secret:e.google.clientSecret,grant_type:"refresh_token",refresh_token:g},t=await fetch(a,{headers:{"Content-Type":"application/json"},method:"POST",body:JSON.stringify(l)});if(!t.ok){let h=await t.json().catch(()=>{});if(t.status===400&&typeof h=="object"&&!!h&&"error"in h&&h.error==="invalid_grant")throw new G;let k=typeof h=="object"&&h&&"error_description"in h?String(h.error_description):"Unknown error";throw new w(`Google token refresh failed: ${t.status} ${k}`)}let o=await t.json(),{access_token:n,expires_in:c}=o;if(!(n&&c))throw r.error("Invalid token response from Google:",o),new Error("Invalid token response from Google");return{accessToken:n,refreshToken:g,expirationDateMs:Number(Date.now())+c*1e3}}catch(a){if(a instanceof w)throw a;let l=a instanceof Error?a.message:"Unknown error";throw new w(`Failed to refresh Google access token: ${l}`)}},y=async d=>{let g=d.get(i).provider;if(!g)throw new Error("Provider not found in session");if(g==="google")return await m(d);throw new Error("Provider not supported")};return{authenticate:async(d,p,g)=>{let{failureRedirect:a}=g||{},l=t=>{if(a===null)return new R(t);if(a==="")throw new TypeError("failureRedirect must not be an empty string; pass null to skip redirecting");let o=a??u;return t!==void 0?(0,H.redirect)(o,{headers:{"Set-Cookie":t}}):(0,H.redirect)(o)};try{if(e.mockUser?.enabled(d))return{accessToken:"mock-access-token",refreshToken:"mock-refresh-token",expirationDateMs:Date.now()+36e5};let t=p.get(i);if(!(t?.accessToken&&t.expirationDateMs))throw l();let{accessToken:o,refreshToken:n,expirationDateMs:c}=t;if(c<Date.now())throw n?new Error("Expired"):l();return{accessToken:o,refreshToken:n,expirationDateMs:c}}catch(t){if(t instanceof Error&&t.message==="Expired")try{let o=await y(p),n=p.get(i);return p.set(i,{...n,...o}),o}catch(o){if(o instanceof G)r.warn("Google refresh token invalid or revoked");else{let c=o instanceof Error?o.message:"Unknown error";r.error("Failed to refresh token:",c)}let n=await s.destroySession(p);throw l(n)}throw t}},refreshedAccessToken:y}};var B=require("react-router");var I=(e,s,r,u)=>{let i=new Map,m=new Map,y=async a=>{let l=i.get(a);if(l!==void 0)return l;let t=m.get(a);if(t!==void 0)return t;let o=(async()=>{try{let n=e.getUserPhoto?await e.getUserPhoto(a):"";return i.set(a,n),n}catch(n){throw i.delete(a),n}finally{m.delete(a)}})();return m.set(a,o),o},S=new WeakMap,d=async a=>{e.warmup&&await e.warmup();let l=await s.getSession(a),t=await s.getSessionUser(a);if(t===void 0)return{kind:"unauthenticated"};let o;try{o=await r.authenticate(a,l,{failureRedirect:null})}catch(A){if(A instanceof R)return A.setCookie!==void 0?{kind:"unauthenticated",cookie:A.setCookie}:{kind:"unauthenticated"};throw u.error("Error in authenticateInLoader:",A),A}let n={accessToken:o.accessToken,refreshToken:o.refreshToken,expirationDateMs:o.expirationDateMs,provider:t.provider},c={id:t.id,displayName:t.displayName,name:t.name,emails:t.emails,...n,role:t.role},h=await y(t.id),x={...t,...n,photo:h},T=o.accessToken!==t.accessToken?await s.saveSession(a,c):void 0;return T!==void 0?{kind:"authenticated",user:x,cookie:T}:{kind:"authenticated",user:x}},p=a=>{let l=S.get(a);if(l!==void 0)return l;let t=d(a);return S.set(a,t),t};async function g(a,l){let{failureRedirect:t}=l;if(t==="")throw new TypeError("failureRedirect must not be an empty string; pass null to skip redirecting");let o=await p(a);if(o.kind==="authenticated")return o.cookie!==void 0?{user:o.user,cookie:o.cookie}:{user:o.user};if(t===null)return o.cookie!==void 0?{cookie:o.cookie}:{};throw o.cookie!==void 0?(0,B.redirect)(t,{headers:{"Set-Cookie":o.cookie}}):(0,B.redirect)(t)}return g};var z=require("@aiquants/auth-core"),M=require("react-router"),W=require("remix-auth");var K=require("remix-auth-oauth2"),te=["openid","https://www.googleapis.com/auth/userinfo.profile","https://www.googleapis.com/auth/userinfo.email"],oe="google",re="https://www.googleapis.com/oauth2/v3/userinfo",L=class extends K.OAuth2Strategy{constructor({clientId:r,clientSecret:u,redirectURI:i,scopes:m,accessType:y,includeGrantedScopes:S,prompt:d,hd:p,loginHint:g},a){let l=m??te;super({clientId:r,clientSecret:u,redirectURI:i,authorizationEndpoint:"https://accounts.google.com/o/oauth2/v2/auth",tokenEndpoint:"https://oauth2.googleapis.com/token",scopes:l},a);this.name=oe;this.scopes=l,this.accessType=y??"online",this.includeGrantedScopes=S??!1,this.prompt=d,this.hd=p,this.loginHint=g}authorizationParams(r,u){return r.set("access_type",this.accessType),r.set("include_granted_scopes",String(this.includeGrantedScopes)),this.scopes&&this.scopes.length>0&&r.set("scope",this.scopes.join(" ")),this.prompt&&r.set("prompt",this.prompt),this.hd&&r.set("hd",this.hd),this.loginHint&&r.set("login_hint",this.loginHint),r}static async userProfile(r){let u=await fetch(re,{headers:{Authorization:`Bearer ${r.accessToken()}`}});if(!u.ok)throw new Error(`Failed to fetch user profile: ${u.statusText}`);let i=await u.json();return{id:i.sub,displayName:i.name,name:{familyName:i.family_name,givenName:i.given_name},emails:[{value:i.email}],photos:[{value:i.picture}],_json:i}}};var N={info:(...e)=>console.info("[Auth]",...e),warn:(...e)=>console.warn("[Auth]",...e),error:(...e)=>console.error("[Auth]",...e)};var D=e=>{let s=e.logger??N,r=(0,z.resolveAuthMessages)(e.messages),u=e.loginPath??"/auth/login",i=new L({clientId:e.google.clientId,clientSecret:e.google.clientSecret,redirectURI:e.google.redirectURI,scopes:e.google.scopes,accessType:e.google.accessType??"offline",includeGrantedScopes:e.google.includeGrantedScopes??!0,prompt:e.google.prompt??"consent"},async({request:y,tokens:S})=>{let{access_token:d,refresh_token:p,expires_in:g,scope:a,id_token:l}=S.data,{_json:{sub:t,email:o,picture:n,hd:c,email_verified:h},photos:x,...k}=await L.userProfile(S),{id:T,displayName:A,name:{familyName:ne,givenName:ie},emails:ae}=k,j="google";if(d===void 0||p===void 0||g===void 0||a===void 0||T===void 0||A===void 0||t===void 0||o===void 0||h===void 0){if(h===!1){let U=encodeURIComponent(r.emailNotVerified);throw(0,M.redirect)(`${u}?error=${U}`)}let f=encodeURIComponent(r.googleFailed);throw(0,M.redirect)(`${u}?error=${f}`)}s.info("Authenticated on Google: ",k.displayName);try{if(e.backendAuth){if(!await e.backendAuth.verify(j,d)){s.error("Backend verification returned false for user:",k.displayName);let v=encodeURIComponent(r.backendFailed);throw(0,M.redirect)(`${u}?error=${v}`)}s.info("Authenticated on Backend: ",k.displayName)}let f=k.emails[0]?.value;if(!await e.isEmailAllowed(f)){let P=new Error("You are not allowed to access this application");throw s.error(P),P}if(e.isUserActive&&!await e.isUserActive(k)){let P=new Error("You are not allowed to access this application");throw s.error("Deactivated account attempted to sign in:",f),P}e.backendAuth&&await e.backendAuth.signup(j,d,k),s.info("Authenticated complete: ",k.displayName);let U=Date.now()+g*1e3;return{...k,...{accessToken:d,refreshToken:p,expirationDateMs:U,provider:j},photo:n}}catch(f){let U=f;typeof f=="object"&&f!==null&&"cause"in f&&(U=f.cause||f),s.error("Error during backend authentication or subsequent processing:",U);let q=r.unexpected,_=!1;if(typeof f=="object"&&f!==null&&"cause"in f){let v=f.cause;v&&typeof v=="object"&&"message"in v&&typeof v.message=="string"&&v.message.includes("getaddrinfo ENOTFOUND")&&(q=r.backendUnreachable,_=!0)}if(!_&&f instanceof Error&&f.message.includes("You are not allowed to access this application")&&(q=r.notAllowed,_=!0),!_&&f instanceof Response&&f.headers.get("Location")?.startsWith(u))throw f;let P=encodeURIComponent(q);throw(0,M.redirect)(`${u}?error=${P}`)}}),m=new W.Authenticator;return m.use(i),m};var E=require("react-router"),O=(e,s,r,u)=>{let i=e.loginPath??"/auth/login",m=e.postLoginRedirect??"/",y=e.postLogoutRedirect??"/",S=async({request:t})=>{if(await s.getSessionUser(t))throw(0,E.redirect)(m);return{error:new URL(t.url).searchParams.get("error")}},d=async({request:t})=>{let o=await t.clone().formData(),n=String(o.get("_action")),c=null;return n==="Sign In with Google"?c=await r.authenticate("google",t):c=null,c},p=async t=>await s.logout(t),g=async t=>{let o=await p(t),n=new Headers;return n.append("Set-Cookie",o),(0,E.redirect)(y,{headers:n})};return{loginLoader:S,loginAction:d,logoutLoader:async({request:t})=>{throw await g(t)},logout:p,logoutAndRedirect:g,googleCallbackLoader:async({request:t})=>{try{let o=await r.authenticate("google",t),h=(await s.getSession(t)).get(s.sessionKey)?.refreshToken,x={accessToken:o.accessToken,refreshToken:o.refreshToken||h,expirationDateMs:o.expirationDateMs,provider:o.provider},k={id:o.id,displayName:o.displayName,name:o.name,emails:o.emails,...x,role:o.role},T=await s.saveSession(t,k);throw(0,E.redirect)(m,{headers:{"Set-Cookie":T}})}catch(o){throw o instanceof Response?o:(u.error("Authentication error:",o),(0,E.redirect)(i))}}}};var b=require("react-router"),F=(e,s)=>{if(!e.secrets||e.secrets.length===0||e.secrets.some(n=>!n))throw new Error("REMIX_SESSION_SECRET must be set");let r=e.sessionKey??"user",u=e.maxAge??3600*24*30,i=s?.loginPath??"/auth/login",m=s?.postLoginRedirect??"/",y=s?.mockUser,S=s?.isUserActive,d=(0,b.createCookieSessionStorage)({cookie:{name:e.cookieName??"__session",httpOnly:!0,path:"/",sameSite:"lax",secrets:e.secrets,secure:e.secure??process.env.NODE_ENV==="production",maxAge:u}}),p=async n=>await d.getSession(n.headers.get("Cookie")),g=async n=>{if(y?.enabled(n))return y.resolve(n);let h=(await p(n)).get(r);if(h!==void 0&&!(S&&!await S(h)))return h},a=async(n,c)=>{let h=await p(n);return h.set(r,c),await d.commitSession(h)},l=async n=>{let c=await g(n);if(c===void 0)throw(0,b.redirect)(i);return c};return{sessionStorage:d,sessionKey:r,getSession:p,getSessionUser:g,saveSession:a,requireUser:l,requireAdminUser:async n=>{let c=await l(n);if(c?.role!=="admin")throw(0,b.redirect)(m);return c},logout:async n=>{let c=await p(n);return await d.destroySession(c)},commitSession:d.commitSession,destroySession:d.destroySession}};var Y=e=>{let s=e.logger??N,r=F(e.session,{mockUser:e.mockUser,loginPath:e.loginPath,postLoginRedirect:e.postLoginRedirect,isUserActive:e.isUserActive}),u=D(e),i=C(e,r,s),m=I(e,r,i,s),y=O(e,r,u,s);return{authenticator:u,storage:r,authFns:i,sessionStorage:r.sessionStorage,getSession:r.getSession,getSessionUser:r.getSessionUser,saveSession:r.saveSession,requireUser:r.requireUser,requireAdminUser:r.requireAdminUser,commitSession:r.commitSession,destroySession:r.destroySession,authenticate:i.authenticate,refreshedAccessToken:i.refreshedAccessToken,authenticateInLoader:m,...y}};0&&(module.exports={AuthRequiredError,GoogleInvalidGrantError,TokenRefreshError,createAuthHandlers,createAuthServer,createAuthSessionStorage,createAuthenticate,createAuthenticateInLoader,createGoogleAuthenticator});
|
|
2
|
-
//# sourceMappingURL=server.js.map
|
package/dist/server.mjs
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
1
|
import{redirect as j}from"react-router";var v=class extends Error{constructor(i){super(i),this.name="TokenRefreshError"}},_=class extends v{constructor(){super("Google refresh token invalid or revoked"),this.name="GoogleInvalidGrantError"}},U=class extends Error{constructor(i){super("Authentication required"),this.name="AuthRequiredError",this.setCookie=i}},I=(e,i,r)=>{let d=e.loginPath??"/auth/login",a=i.sessionKey,m=async l=>{let g=l.get(a)?.refreshToken;if(!g)throw new Error("Refresh token not found in session");try{let n="https://www.googleapis.com/oauth2/v4/token",u={client_id:e.google.clientId,client_secret:e.google.clientSecret,grant_type:"refresh_token",refresh_token:g},t=await fetch(n,{headers:{"Content-Type":"application/json"},method:"POST",body:JSON.stringify(u)});if(!t.ok){let h=await t.json().catch(()=>{});if(t.status===400&&typeof h=="object"&&!!h&&"error"in h&&h.error==="invalid_grant")throw new _;let k=typeof h=="object"&&h&&"error_description"in h?String(h.error_description):"Unknown error";throw new v(`Google token refresh failed: ${t.status} ${k}`)}let o=await t.json(),{access_token:s,expires_in:c}=o;if(!(s&&c))throw r.error("Invalid token response from Google:",o),new Error("Invalid token response from Google");return{accessToken:s,refreshToken:g,expirationDateMs:Number(Date.now())+c*1e3}}catch(n){if(n instanceof v)throw n;let u=n instanceof Error?n.message:"Unknown error";throw new v(`Failed to refresh Google access token: ${u}`)}},y=async l=>{let g=l.get(a).provider;if(!g)throw new Error("Provider not found in session");if(g==="google")return await m(l);throw new Error("Provider not supported")};return{authenticate:async(l,p,g)=>{let{failureRedirect:n}=g||{},u=t=>{if(n===null)return new U(t);if(n==="")throw new TypeError("failureRedirect must not be an empty string; pass null to skip redirecting");let o=n??d;return t!==void 0?j(o,{headers:{"Set-Cookie":t}}):j(o)};try{if(e.mockUser?.enabled(l))return{accessToken:"mock-access-token",refreshToken:"mock-refresh-token",expirationDateMs:Date.now()+36e5};let t=p.get(a);if(!(t?.accessToken&&t.expirationDateMs))throw u();let{accessToken:o,refreshToken:s,expirationDateMs:c}=t;if(c<Date.now())throw s?new Error("Expired"):u();return{accessToken:o,refreshToken:s,expirationDateMs:c}}catch(t){if(t instanceof Error&&t.message==="Expired")try{let o=await y(p),s=p.get(a);return p.set(a,{...s,...o}),o}catch(o){if(o instanceof _)r.warn("Google refresh token invalid or revoked");else{let c=o instanceof Error?o.message:"Unknown error";r.error("Failed to refresh token:",c)}let s=await i.destroySession(p);throw u(s)}throw t}},refreshedAccessToken:y}};import{redirect as $}from"react-router";var N=(e,i,r,d)=>{let a=new Map,m=new Map,y=async n=>{let u=a.get(n);if(u!==void 0)return u;let t=m.get(n);if(t!==void 0)return t;let o=(async()=>{try{let s=e.getUserPhoto?await e.getUserPhoto(n):"";return a.set(n,s),s}catch(s){throw a.delete(n),s}finally{m.delete(n)}})();return m.set(n,o),o},S=new WeakMap,l=async n=>{e.warmup&&await e.warmup();let u=await i.getSession(n),t=await i.getSessionUser(n);if(t===void 0)return{kind:"unauthenticated"};let o;try{o=await r.authenticate(n,u,{failureRedirect:null})}catch(w){if(w instanceof U)return w.setCookie!==void 0?{kind:"unauthenticated",cookie:w.setCookie}:{kind:"unauthenticated"};throw d.error("Error in authenticateInLoader:",w),w}let s={accessToken:o.accessToken,refreshToken:o.refreshToken,expirationDateMs:o.expirationDateMs,provider:t.provider},c={id:t.id,displayName:t.displayName,name:t.name,emails:t.emails,...s,role:t.role},h=await y(t.id),R={...t,...s,photo:h},x=o.accessToken!==t.accessToken?await i.saveSession(n,c):void 0;return x!==void 0?{kind:"authenticated",user:R,cookie:x}:{kind:"authenticated",user:R}},p=n=>{let u=S.get(n);if(u!==void 0)return u;let t=l(n);return S.set(n,t),t};async function g(n,u){let{failureRedirect:t}=u;if(t==="")throw new TypeError("failureRedirect must not be an empty string; pass null to skip redirecting");let o=await p(n);if(o.kind==="authenticated")return o.cookie!==void 0?{user:o.user,cookie:o.cookie}:{user:o.user};if(t===null)return o.cookie!==void 0?{cookie:o.cookie}:{};throw o.cookie!==void 0?$(t,{headers:{"Set-Cookie":o.cookie}}):$(t)}return g};import{resolveAuthMessages as Y}from"@aiquants/auth-core";import{redirect as b}from"react-router";import{Authenticator as V}from"remix-auth";import{OAuth2Strategy as B}from"remix-auth-oauth2";var K=["openid","https://www.googleapis.com/auth/userinfo.profile","https://www.googleapis.com/auth/userinfo.email"],z="google",W="https://www.googleapis.com/oauth2/v3/userinfo",L=class extends B{constructor({clientId:r,clientSecret:d,redirectURI:a,scopes:m,accessType:y,includeGrantedScopes:S,prompt:l,hd:p,loginHint:g},n){let u=m??K;super({clientId:r,clientSecret:d,redirectURI:a,authorizationEndpoint:"https://accounts.google.com/o/oauth2/v2/auth",tokenEndpoint:"https://oauth2.googleapis.com/token",scopes:u},n);this.name=z;this.scopes=u,this.accessType=y??"online",this.includeGrantedScopes=S??!1,this.prompt=l,this.hd=p,this.loginHint=g}authorizationParams(r,d){return r.set("access_type",this.accessType),r.set("include_granted_scopes",String(this.includeGrantedScopes)),this.scopes&&this.scopes.length>0&&r.set("scope",this.scopes.join(" ")),this.prompt&&r.set("prompt",this.prompt),this.hd&&r.set("hd",this.hd),this.loginHint&&r.set("login_hint",this.loginHint),r}static async userProfile(r){let d=await fetch(W,{headers:{Authorization:`Bearer ${r.accessToken()}`}});if(!d.ok)throw new Error(`Failed to fetch user profile: ${d.statusText}`);let a=await d.json();return{id:a.sub,displayName:a.name,name:{familyName:a.family_name,givenName:a.given_name},emails:[{value:a.email}],photos:[{value:a.picture}],_json:a}}};var E={info:(...e)=>console.info("[Auth]",...e),warn:(...e)=>console.warn("[Auth]",...e),error:(...e)=>console.error("[Auth]",...e)};var D=e=>{let i=e.logger??E,r=Y(e.messages),d=e.loginPath??"/auth/login",a=new L({clientId:e.google.clientId,clientSecret:e.google.clientSecret,redirectURI:e.google.redirectURI,scopes:e.google.scopes,accessType:e.google.accessType??"offline",includeGrantedScopes:e.google.includeGrantedScopes??!0,prompt:e.google.prompt??"consent"},async({request:y,tokens:S})=>{let{access_token:l,refresh_token:p,expires_in:g,scope:n,id_token:u}=S.data,{_json:{sub:t,email:o,picture:s,hd:c,email_verified:h},photos:R,...k}=await L.userProfile(S),{id:x,displayName:w,name:{familyName:X,givenName:Z},emails:ee}=k,C="google";if(l===void 0||p===void 0||g===void 0||n===void 0||x===void 0||w===void 0||t===void 0||o===void 0||h===void 0){if(h===!1){let T=encodeURIComponent(r.emailNotVerified);throw b(`${d}?error=${T}`)}let f=encodeURIComponent(r.googleFailed);throw b(`${d}?error=${f}`)}i.info("Authenticated on Google: ",k.displayName);try{if(e.backendAuth){if(!await e.backendAuth.verify(C,l)){i.error("Backend verification returned false for user:",k.displayName);let P=encodeURIComponent(r.backendFailed);throw b(`${d}?error=${P}`)}i.info("Authenticated on Backend: ",k.displayName)}let f=k.emails[0]?.value;if(!await e.isEmailAllowed(f)){let A=new Error("You are not allowed to access this application");throw i.error(A),A}if(e.isUserActive&&!await e.isUserActive(k)){let A=new Error("You are not allowed to access this application");throw i.error("Deactivated account attempted to sign in:",f),A}e.backendAuth&&await e.backendAuth.signup(C,l,k),i.info("Authenticated complete: ",k.displayName);let T=Date.now()+g*1e3;return{...k,...{accessToken:l,refreshToken:p,expirationDateMs:T,provider:C},photo:s}}catch(f){let T=f;typeof f=="object"&&f!==null&&"cause"in f&&(T=f.cause||f),i.error("Error during backend authentication or subsequent processing:",T);let M=r.unexpected,G=!1;if(typeof f=="object"&&f!==null&&"cause"in f){let P=f.cause;P&&typeof P=="object"&&"message"in P&&typeof P.message=="string"&&P.message.includes("getaddrinfo ENOTFOUND")&&(M=r.backendUnreachable,G=!0)}if(!G&&f instanceof Error&&f.message.includes("You are not allowed to access this application")&&(M=r.notAllowed,G=!0),!G&&f instanceof Response&&f.headers.get("Location")?.startsWith(d))throw f;let A=encodeURIComponent(M);throw b(`${d}?error=${A}`)}}),m=new V;return m.use(a),m};import{redirect as q}from"react-router";var O=(e,i,r,d)=>{let a=e.loginPath??"/auth/login",m=e.postLoginRedirect??"/",y=e.postLogoutRedirect??"/",S=async({request:t})=>{if(await i.getSessionUser(t))throw q(m);return{error:new URL(t.url).searchParams.get("error")}},l=async({request:t})=>{let o=await t.clone().formData(),s=String(o.get("_action")),c=null;return s==="Sign In with Google"?c=await r.authenticate("google",t):c=null,c},p=async t=>await i.logout(t),g=async t=>{let o=await p(t),s=new Headers;return s.append("Set-Cookie",o),q(y,{headers:s})};return{loginLoader:S,loginAction:l,logoutLoader:async({request:t})=>{throw await g(t)},logout:p,logoutAndRedirect:g,googleCallbackLoader:async({request:t})=>{try{let o=await r.authenticate("google",t),h=(await i.getSession(t)).get(i.sessionKey)?.refreshToken,R={accessToken:o.accessToken,refreshToken:o.refreshToken||h,expirationDateMs:o.expirationDateMs,provider:o.provider},k={id:o.id,displayName:o.displayName,name:o.name,emails:o.emails,...R,role:o.role},x=await i.saveSession(t,k);throw q(m,{headers:{"Set-Cookie":x}})}catch(o){throw o instanceof Response?o:(d.error("Authentication error:",o),q(a))}}}};import{createCookieSessionStorage as J,redirect as H}from"react-router";var F=(e,i)=>{if(!e.secrets||e.secrets.length===0||e.secrets.some(s=>!s))throw new Error("REMIX_SESSION_SECRET must be set");let r=e.sessionKey??"user",d=e.maxAge??3600*24*30,a=i?.loginPath??"/auth/login",m=i?.postLoginRedirect??"/",y=i?.mockUser,S=i?.isUserActive,l=J({cookie:{name:e.cookieName??"__session",httpOnly:!0,path:"/",sameSite:"lax",secrets:e.secrets,secure:e.secure??process.env.NODE_ENV==="production",maxAge:d}}),p=async s=>await l.getSession(s.headers.get("Cookie")),g=async s=>{if(y?.enabled(s))return y.resolve(s);let h=(await p(s)).get(r);if(h!==void 0&&!(S&&!await S(h)))return h},n=async(s,c)=>{let h=await p(s);return h.set(r,c),await l.commitSession(h)},u=async s=>{let c=await g(s);if(c===void 0)throw H(a);return c};return{sessionStorage:l,sessionKey:r,getSession:p,getSessionUser:g,saveSession:n,requireUser:u,requireAdminUser:async s=>{let c=await u(s);if(c?.role!=="admin")throw H(m);return c},logout:async s=>{let c=await p(s);return await l.destroySession(c)},commitSession:l.commitSession,destroySession:l.destroySession}};var Q=e=>{let i=e.logger??E,r=F(e.session,{mockUser:e.mockUser,loginPath:e.loginPath,postLoginRedirect:e.postLoginRedirect,isUserActive:e.isUserActive}),d=D(e),a=I(e,r,i),m=N(e,r,a,i),y=O(e,r,d,i);return{authenticator:d,storage:r,authFns:a,sessionStorage:r.sessionStorage,getSession:r.getSession,getSessionUser:r.getSessionUser,saveSession:r.saveSession,requireUser:r.requireUser,requireAdminUser:r.requireAdminUser,commitSession:r.commitSession,destroySession:r.destroySession,authenticate:a.authenticate,refreshedAccessToken:a.refreshedAccessToken,authenticateInLoader:m,...y}};export{U as AuthRequiredError,_ as GoogleInvalidGrantError,v as TokenRefreshError,O as createAuthHandlers,Q as createAuthServer,F as createAuthSessionStorage,I as createAuthenticate,N as createAuthenticateInLoader,D as createGoogleAuthenticator};
|
|
2
|
-
//# sourceMappingURL=server.mjs.map
|