@aiquants/auth-react-router 0.7.3 → 0.9.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 +4 -1
- package/dist/server.d.mts +45 -20
- package/dist/server.d.ts +45 -20
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +1 -1
- package/dist/server.mjs.map +1 -1
- package/dist/styles/auth-react-router.standalone.css +1 -1
- package/package.json +3 -2
- package/src/server/authenticate-in-loader.ts +158 -63
- package/src/server/authenticate.ts +40 -7
- package/src/server.ts +1 -1
package/README.md
CHANGED
|
@@ -106,7 +106,10 @@ Never mix the two: importing the standalone utility CSS next to a host Tailwind
|
|
|
106
106
|
- **Session cookie byte compatibility**: `__session` (httpOnly/lax/30d), key `"user"`, value = `{ id, displayName, name, emails, accessToken, refreshToken?, expirationDateMs?, provider, role? }` — never `photo`/`_json`/`photos`. Changing this invalidates all live 30-day sessions.
|
|
107
107
|
- **Verify order**: Google profile check → `backendAuth.verify` → allowlist → `backendAuth.signup` (every login). Failures redirect to `<loginPath>?error=<encoded error message>` (messages overridable via `messages`).
|
|
108
108
|
- **Token refresh**: expiry check → Google `oauth2/v4/token` rotate → session update; `invalid_grant` destroys the session and redirects to login with `Set-Cookie`.
|
|
109
|
-
- **Loader guard**: `authenticateInLoader(request, { failureRedirect })` returns `{ user
|
|
109
|
+
- **Loader guard**: `authenticateInLoader(request, { failureRedirect })` returns `{ user, cookie? }` with `user` **required** when `failureRedirect` is a string (any authentication failure throws a redirect), and `{ user?, cookie? }` when it is `null` (no-redirect mode). Passing `""` throws a `TypeError` before any work — it is a mistake, not a request for the default.
|
|
110
|
+
- **One authentication per request**: the guard performs its work at most once per `Request` instance and shares a mode-neutral result, so a root loader (`null`) and a leaf loader (`"/auth/login"`) in the same React Router data pass share a single pass — one warmup, one token refresh, one `Set-Cookie`. The failure policy is applied per caller on top of the shared result, so it must never be part of the cache key.
|
|
111
|
+
- **Infrastructure failures are never laundered into logouts**: anything that is not an authentication failure (an unexpected error inside the auth step, a `getUserPhoto` port outage, a `saveSession` failure) propagates unchanged in **both** modes. The session is not destroyed and no redirect is produced.
|
|
112
|
+
- Photo lookups are cached with in-flight dedup per factory instance.
|
|
110
113
|
|
|
111
114
|
## DI Ports Absorbing Per-App Differences
|
|
112
115
|
|
package/dist/server.d.mts
CHANGED
|
@@ -143,7 +143,7 @@ declare const createAuthServer: (config: AuthServerConfig) => {
|
|
|
143
143
|
};
|
|
144
144
|
authFns: {
|
|
145
145
|
authenticate: (request: Request, session: react_router.Session, options?: {
|
|
146
|
-
failureRedirect?: string;
|
|
146
|
+
failureRedirect?: string | null;
|
|
147
147
|
}) => Promise<{
|
|
148
148
|
accessToken: any;
|
|
149
149
|
refreshToken: any;
|
|
@@ -164,7 +164,7 @@ declare const createAuthServer: (config: AuthServerConfig) => {
|
|
|
164
164
|
commitSession: (session: react_router.Session<react_router.SessionData, react_router.SessionData>, options?: react_router.CookieSerializeOptions) => Promise<string>;
|
|
165
165
|
destroySession: (session: react_router.Session<react_router.SessionData, react_router.SessionData>, options?: react_router.CookieSerializeOptions) => Promise<string>;
|
|
166
166
|
authenticate: (request: Request, session: react_router.Session, options?: {
|
|
167
|
-
failureRedirect?: string;
|
|
167
|
+
failureRedirect?: string | null;
|
|
168
168
|
}) => Promise<{
|
|
169
169
|
accessToken: any;
|
|
170
170
|
refreshToken: any;
|
|
@@ -175,14 +175,20 @@ declare const createAuthServer: (config: AuthServerConfig) => {
|
|
|
175
175
|
refreshToken: any;
|
|
176
176
|
expirationDateMs: number;
|
|
177
177
|
}>;
|
|
178
|
-
authenticateInLoader:
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
178
|
+
authenticateInLoader: {
|
|
179
|
+
(request: Request, options: {
|
|
180
|
+
failureRedirect: string;
|
|
181
|
+
}): Promise<{
|
|
182
|
+
user: _aiquants_auth_core.MyGoogleProfile;
|
|
183
|
+
cookie?: string;
|
|
184
|
+
}>;
|
|
185
|
+
(request: Request, options: {
|
|
186
|
+
failureRedirect: null;
|
|
187
|
+
}): Promise<{
|
|
188
|
+
user?: _aiquants_auth_core.MyGoogleProfile;
|
|
189
|
+
cookie?: string;
|
|
190
|
+
}>;
|
|
191
|
+
};
|
|
186
192
|
};
|
|
187
193
|
|
|
188
194
|
/** createAuthSessionStorage が返すセッション操作束。 */
|
|
@@ -219,6 +225,19 @@ declare class TokenRefreshError extends Error {
|
|
|
219
225
|
declare class GoogleInvalidGrantError extends TokenRefreshError {
|
|
220
226
|
constructor();
|
|
221
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Signals that authentication is required when the caller opted out of redirecting.
|
|
230
|
+
* 呼び出し側がリダイレクトを拒否した (`failureRedirect: null`) 際に認証要求を伝えるエラー。
|
|
231
|
+
*
|
|
232
|
+
* `redirect()` を投げてしまうと React Router がそれを本物の 302 として扱うため、
|
|
233
|
+
* 「リダイレクトしない」という契約を型で表現できるこのエラーへ置き換える。
|
|
234
|
+
* セッション破棄が発生した場合はその `Set-Cookie` を `setCookie` で運ぶ
|
|
235
|
+
* (捨てると無効セッションがブラウザに残り続ける)。
|
|
236
|
+
*/
|
|
237
|
+
declare class AuthRequiredError extends Error {
|
|
238
|
+
readonly setCookie?: string;
|
|
239
|
+
constructor(setCookie?: string);
|
|
240
|
+
}
|
|
222
241
|
/** createAuthenticate が返す認証関数束。 */
|
|
223
242
|
type AuthenticateFns = ReturnType<typeof createAuthenticate>;
|
|
224
243
|
/**
|
|
@@ -227,7 +246,7 @@ type AuthenticateFns = ReturnType<typeof createAuthenticate>;
|
|
|
227
246
|
*/
|
|
228
247
|
declare const createAuthenticate: (config: AuthServerConfig, storage: AuthSessionStorage, logger: LoggerPort) => {
|
|
229
248
|
authenticate: (request: Request, session: Session, options?: {
|
|
230
|
-
failureRedirect?: string;
|
|
249
|
+
failureRedirect?: string | null;
|
|
231
250
|
}) => Promise<{
|
|
232
251
|
accessToken: any;
|
|
233
252
|
refreshToken: any;
|
|
@@ -255,14 +274,20 @@ declare const createAuthenticate: (config: AuthServerConfig, storage: AuthSessio
|
|
|
255
274
|
* Builds the per-request loader guard wired to injected ports.
|
|
256
275
|
* 注入ポートに結線した毎リクエストローダーガードを生成する。
|
|
257
276
|
*/
|
|
258
|
-
declare const createAuthenticateInLoader: (config: AuthServerConfig, storage: AuthSessionStorage, authFns: AuthenticateFns, logger: LoggerPort) =>
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
277
|
+
declare const createAuthenticateInLoader: (config: AuthServerConfig, storage: AuthSessionStorage, authFns: AuthenticateFns, logger: LoggerPort) => {
|
|
278
|
+
(request: Request, options: {
|
|
279
|
+
failureRedirect: string;
|
|
280
|
+
}): Promise<{
|
|
281
|
+
user: MyGoogleProfile;
|
|
282
|
+
cookie?: string;
|
|
283
|
+
}>;
|
|
284
|
+
(request: Request, options: {
|
|
285
|
+
failureRedirect: null;
|
|
286
|
+
}): Promise<{
|
|
287
|
+
user?: MyGoogleProfile;
|
|
288
|
+
cookie?: string;
|
|
289
|
+
}>;
|
|
290
|
+
};
|
|
266
291
|
|
|
267
292
|
/**
|
|
268
293
|
* @module google-strategy
|
|
@@ -315,4 +340,4 @@ declare const createAuthHandlers: (config: AuthServerConfig, storage: AuthSessio
|
|
|
315
340
|
}) => Promise<never>;
|
|
316
341
|
};
|
|
317
342
|
|
|
318
|
-
export { 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 };
|
|
343
|
+
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
|
@@ -143,7 +143,7 @@ declare const createAuthServer: (config: AuthServerConfig) => {
|
|
|
143
143
|
};
|
|
144
144
|
authFns: {
|
|
145
145
|
authenticate: (request: Request, session: react_router.Session, options?: {
|
|
146
|
-
failureRedirect?: string;
|
|
146
|
+
failureRedirect?: string | null;
|
|
147
147
|
}) => Promise<{
|
|
148
148
|
accessToken: any;
|
|
149
149
|
refreshToken: any;
|
|
@@ -164,7 +164,7 @@ declare const createAuthServer: (config: AuthServerConfig) => {
|
|
|
164
164
|
commitSession: (session: react_router.Session<react_router.SessionData, react_router.SessionData>, options?: react_router.CookieSerializeOptions) => Promise<string>;
|
|
165
165
|
destroySession: (session: react_router.Session<react_router.SessionData, react_router.SessionData>, options?: react_router.CookieSerializeOptions) => Promise<string>;
|
|
166
166
|
authenticate: (request: Request, session: react_router.Session, options?: {
|
|
167
|
-
failureRedirect?: string;
|
|
167
|
+
failureRedirect?: string | null;
|
|
168
168
|
}) => Promise<{
|
|
169
169
|
accessToken: any;
|
|
170
170
|
refreshToken: any;
|
|
@@ -175,14 +175,20 @@ declare const createAuthServer: (config: AuthServerConfig) => {
|
|
|
175
175
|
refreshToken: any;
|
|
176
176
|
expirationDateMs: number;
|
|
177
177
|
}>;
|
|
178
|
-
authenticateInLoader:
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
178
|
+
authenticateInLoader: {
|
|
179
|
+
(request: Request, options: {
|
|
180
|
+
failureRedirect: string;
|
|
181
|
+
}): Promise<{
|
|
182
|
+
user: _aiquants_auth_core.MyGoogleProfile;
|
|
183
|
+
cookie?: string;
|
|
184
|
+
}>;
|
|
185
|
+
(request: Request, options: {
|
|
186
|
+
failureRedirect: null;
|
|
187
|
+
}): Promise<{
|
|
188
|
+
user?: _aiquants_auth_core.MyGoogleProfile;
|
|
189
|
+
cookie?: string;
|
|
190
|
+
}>;
|
|
191
|
+
};
|
|
186
192
|
};
|
|
187
193
|
|
|
188
194
|
/** createAuthSessionStorage が返すセッション操作束。 */
|
|
@@ -219,6 +225,19 @@ declare class TokenRefreshError extends Error {
|
|
|
219
225
|
declare class GoogleInvalidGrantError extends TokenRefreshError {
|
|
220
226
|
constructor();
|
|
221
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Signals that authentication is required when the caller opted out of redirecting.
|
|
230
|
+
* 呼び出し側がリダイレクトを拒否した (`failureRedirect: null`) 際に認証要求を伝えるエラー。
|
|
231
|
+
*
|
|
232
|
+
* `redirect()` を投げてしまうと React Router がそれを本物の 302 として扱うため、
|
|
233
|
+
* 「リダイレクトしない」という契約を型で表現できるこのエラーへ置き換える。
|
|
234
|
+
* セッション破棄が発生した場合はその `Set-Cookie` を `setCookie` で運ぶ
|
|
235
|
+
* (捨てると無効セッションがブラウザに残り続ける)。
|
|
236
|
+
*/
|
|
237
|
+
declare class AuthRequiredError extends Error {
|
|
238
|
+
readonly setCookie?: string;
|
|
239
|
+
constructor(setCookie?: string);
|
|
240
|
+
}
|
|
222
241
|
/** createAuthenticate が返す認証関数束。 */
|
|
223
242
|
type AuthenticateFns = ReturnType<typeof createAuthenticate>;
|
|
224
243
|
/**
|
|
@@ -227,7 +246,7 @@ type AuthenticateFns = ReturnType<typeof createAuthenticate>;
|
|
|
227
246
|
*/
|
|
228
247
|
declare const createAuthenticate: (config: AuthServerConfig, storage: AuthSessionStorage, logger: LoggerPort) => {
|
|
229
248
|
authenticate: (request: Request, session: Session, options?: {
|
|
230
|
-
failureRedirect?: string;
|
|
249
|
+
failureRedirect?: string | null;
|
|
231
250
|
}) => Promise<{
|
|
232
251
|
accessToken: any;
|
|
233
252
|
refreshToken: any;
|
|
@@ -255,14 +274,20 @@ declare const createAuthenticate: (config: AuthServerConfig, storage: AuthSessio
|
|
|
255
274
|
* Builds the per-request loader guard wired to injected ports.
|
|
256
275
|
* 注入ポートに結線した毎リクエストローダーガードを生成する。
|
|
257
276
|
*/
|
|
258
|
-
declare const createAuthenticateInLoader: (config: AuthServerConfig, storage: AuthSessionStorage, authFns: AuthenticateFns, logger: LoggerPort) =>
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
277
|
+
declare const createAuthenticateInLoader: (config: AuthServerConfig, storage: AuthSessionStorage, authFns: AuthenticateFns, logger: LoggerPort) => {
|
|
278
|
+
(request: Request, options: {
|
|
279
|
+
failureRedirect: string;
|
|
280
|
+
}): Promise<{
|
|
281
|
+
user: MyGoogleProfile;
|
|
282
|
+
cookie?: string;
|
|
283
|
+
}>;
|
|
284
|
+
(request: Request, options: {
|
|
285
|
+
failureRedirect: null;
|
|
286
|
+
}): Promise<{
|
|
287
|
+
user?: MyGoogleProfile;
|
|
288
|
+
cookie?: string;
|
|
289
|
+
}>;
|
|
290
|
+
};
|
|
266
291
|
|
|
267
292
|
/**
|
|
268
293
|
* @module google-strategy
|
|
@@ -315,4 +340,4 @@ declare const createAuthHandlers: (config: AuthServerConfig, storage: AuthSessio
|
|
|
315
340
|
}) => Promise<never>;
|
|
316
341
|
};
|
|
317
342
|
|
|
318
|
-
export { 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 };
|
|
343
|
+
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,2 @@
|
|
|
1
|
-
"use strict";var $=Object.defineProperty;var V=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var J=Object.prototype.hasOwnProperty;var Q=(e,r)=>{for(var o in r)$(e,o,{get:r[o],enumerable:!0})},X=(e,r,o,u)=>{if(r&&typeof r=="object"||typeof r=="function")for(let n of W(r))!J.call(e,n)&&n!==o&&$(e,n,{get:()=>r[n],enumerable:!(u=V(r,n))||u.enumerable});return e};var Z=e=>X($({},"__esModule",{value:!0}),e);var re={};Q(re,{GoogleInvalidGrantError:()=>x,TokenRefreshError:()=>k,createAuthHandlers:()=>D,createAuthServer:()=>z,createAuthSessionStorage:()=>O,createAuthenticate:()=>b,createAuthenticateInLoader:()=>I,createGoogleAuthenticator:()=>q});module.exports=Z(re);var E=require("react-router"),k=class extends Error{constructor(r){super(r),this.name="TokenRefreshError"}},x=class extends k{constructor(){super("Google refresh token invalid or revoked"),this.name="GoogleInvalidGrantError"}},b=(e,r,o)=>{let u=e.loginPath??"/auth/login",n=r.sessionKey,m=async a=>{let l=a.get(n)?.refreshToken;if(!l)throw new Error("Refresh token not found in session");try{let h="https://www.googleapis.com/oauth2/v4/token",c={client_id:e.google.clientId,client_secret:e.google.clientSecret,grant_type:"refresh_token",refresh_token:l},t=await fetch(h,{headers:{"Content-Type":"application/json"},method:"POST",body:JSON.stringify(c)});if(!t.ok){let p=await t.json().catch(()=>{});if(t.status===400&&typeof p=="object"&&!!p&&"error"in p&&p.error==="invalid_grant")throw new x;let S=typeof p=="object"&&p&&"error_description"in p?String(p.error_description):"Unknown error";throw new k(`Google token refresh failed: ${t.status} ${S}`)}let s=await t.json(),{access_token:i,expires_in:g}=s;if(!(i&&g))throw o.error("Invalid token response from Google:",s),new Error("Invalid token response from Google");return{accessToken:i,refreshToken:l,expirationDateMs:Number(Date.now())+g*1e3}}catch(h){if(h instanceof k)throw h;let c=h instanceof Error?h.message:"Unknown error";throw new k(`Failed to refresh Google access token: ${c}`)}},y=async a=>{let l=a.get(n).provider;if(!l)throw new Error("Provider not found in session");if(l==="google")return await m(a);throw new Error("Provider not supported")};return{authenticate:async(a,d,l)=>{let{failureRedirect:h}=l||{};try{if(e.mockUser?.enabled(a))return{accessToken:"mock-access-token",refreshToken:"mock-refresh-token",expirationDateMs:Date.now()+36e5};let c=d.get(n);if(!(c?.accessToken&&c.expirationDateMs))throw(0,E.redirect)(h||u);let{accessToken:t,refreshToken:s,expirationDateMs:i}=c;if(i<Date.now())throw s?new Error("Expired"):(0,E.redirect)(h||u);return{accessToken:t,refreshToken:s,expirationDateMs:i}}catch(c){if(c instanceof Error&&c.message==="Expired")try{let t=await y(d),s=d.get(n);return d.set(n,{...s,...t}),t}catch(t){if(t instanceof x)o.warn("Google refresh token invalid or revoked");else{let i=t instanceof Error?t.message:"Unknown error";o.error("Failed to refresh token:",i)}let s=await r.destroySession(d);throw(0,E.redirect)(h||u,{headers:{"Set-Cookie":s}})}throw c}},refreshedAccessToken:y}};var C=require("react-router"),I=(e,r,o,u)=>{let n=new Map,m=new Map,y=async a=>{let d=n.get(a);if(d!==void 0)return d;let l=m.get(a);if(l!==void 0)return l;let h=(async()=>{try{let c=e.getUserPhoto?await e.getUserPhoto(a):"";return n.set(a,c),c}catch(c){throw n.delete(a),c}finally{m.delete(a)}})();return m.set(a,h),h};return async(a,d)=>{e.warmup&&await e.warmup();let{failureRedirect:l}=d,h=await r.getSession(a),c=await r.getSessionUser(a);if(c===void 0){if(l===null)return{};throw(0,C.redirect)(l)}try{let t=await o.authenticate(a,h,{failureRedirect:l??void 0}),s={accessToken:t.accessToken,refreshToken:t.refreshToken,expirationDateMs:t.expirationDateMs,provider:c.provider},i={id:c.id,displayName:c.displayName,name:c.name,emails:c.emails,...s,role:c.role},g=await y(c.id),p={...c,...s,photo:g},S=t.accessToken!==c.accessToken?await r.saveSession(a,i):void 0;return{user:p,cookie:S}}catch(t){if(t instanceof Response&&t.headers.get("Location"))throw t;if(u.error("Error in authenticateInLoader:",t),l!==null){let s;try{s=await r.destroySession(h)}catch(i){u.error("Failed to destroy session during auth error:",i)}throw s!==void 0?(0,C.redirect)(l,{headers:{"Set-Cookie":s}}):(0,C.redirect)(l)}throw t}}};var B=require("@aiquants/auth-core"),T=require("react-router"),K=require("remix-auth");var H=require("remix-auth-oauth2"),ee=["openid","https://www.googleapis.com/auth/userinfo.profile","https://www.googleapis.com/auth/userinfo.email"],oe="google",te="https://www.googleapis.com/oauth2/v3/userinfo",U=class extends H.OAuth2Strategy{constructor({clientId:o,clientSecret:u,redirectURI:n,scopes:m,accessType:y,includeGrantedScopes:w,prompt:a,hd:d,loginHint:l},h){let c=m??ee;super({clientId:o,clientSecret:u,redirectURI:n,authorizationEndpoint:"https://accounts.google.com/o/oauth2/v2/auth",tokenEndpoint:"https://oauth2.googleapis.com/token",scopes:c},h);this.name=oe;this.scopes=c,this.accessType=y??"online",this.includeGrantedScopes=w??!1,this.prompt=a,this.hd=d,this.loginHint=l}authorizationParams(o,u){return o.set("access_type",this.accessType),o.set("include_granted_scopes",String(this.includeGrantedScopes)),this.scopes&&this.scopes.length>0&&o.set("scope",this.scopes.join(" ")),this.prompt&&o.set("prompt",this.prompt),this.hd&&o.set("hd",this.hd),this.loginHint&&o.set("login_hint",this.loginHint),o}static async userProfile(o){let u=await fetch(te,{headers:{Authorization:`Bearer ${o.accessToken()}`}});if(!u.ok)throw new Error(`Failed to fetch user profile: ${u.statusText}`);let n=await u.json();return{id:n.sub,displayName:n.name,name:{familyName:n.family_name,givenName:n.given_name},emails:[{value:n.email}],photos:[{value:n.picture}],_json:n}}};var N={info:(...e)=>console.info("[Auth]",...e),warn:(...e)=>console.warn("[Auth]",...e),error:(...e)=>console.error("[Auth]",...e)};var q=e=>{let r=e.logger??N,o=(0,B.resolveAuthMessages)(e.messages),u=e.loginPath??"/auth/login",n=new U({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:w})=>{let{access_token:a,refresh_token:d,expires_in:l,scope:h,id_token:c}=w.data,{_json:{sub:t,email:s,picture:i,hd:g,email_verified:p},photos:L,...S}=await U.userProfile(w),{id:F,displayName:Y,name:{familyName:se,givenName:ne},emails:ie}=S,j="google";if(a===void 0||d===void 0||l===void 0||h===void 0||F===void 0||Y===void 0||t===void 0||s===void 0||p===void 0){if(p===!1){let v=encodeURIComponent(o.emailNotVerified);throw(0,T.redirect)(`${u}?error=${v}`)}let f=encodeURIComponent(o.googleFailed);throw(0,T.redirect)(`${u}?error=${f}`)}r.info("Authenticated on Google: ",S.displayName);try{if(e.backendAuth){if(!await e.backendAuth.verify(j,a)){r.error("Backend verification returned false for user:",S.displayName);let P=encodeURIComponent(o.backendFailed);throw(0,T.redirect)(`${u}?error=${P}`)}r.info("Authenticated on Backend: ",S.displayName)}let f=S.emails[0]?.value;if(!await e.isEmailAllowed(f)){let A=new Error("You are not allowed to access this application");throw r.error(A),A}if(e.isUserActive&&!await e.isUserActive(S)){let A=new Error("You are not allowed to access this application");throw r.error("Deactivated account attempted to sign in:",f),A}e.backendAuth&&await e.backendAuth.signup(j,a),r.info("Authenticated complete: ",S.displayName);let v=Date.now()+l*1e3;return{...S,...{accessToken:a,refreshToken:d,expirationDateMs:v,provider:j},photo:i}}catch(f){let v=f;typeof f=="object"&&f!==null&&"cause"in f&&(v=f.cause||f),r.error("Error during backend authentication or subsequent processing:",v);let M=o.unexpected,R=!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=o.backendUnreachable,R=!0)}if(!R&&f instanceof Error&&f.message.includes("You are not allowed to access this application")&&(M=o.notAllowed,R=!0),!R&&f instanceof Response&&f.headers.get("Location")?.startsWith(u))throw f;let A=encodeURIComponent(M);throw(0,T.redirect)(`${u}?error=${A}`)}}),m=new K.Authenticator;return m.use(n),m};var G=require("react-router"),D=(e,r,o,u)=>{let n=e.loginPath??"/auth/login",m=e.postLoginRedirect??"/",y=e.postLogoutRedirect??"/",w=async({request:t})=>{if(await r.getSessionUser(t))throw(0,G.redirect)(m);return{error:new URL(t.url).searchParams.get("error")}},a=async({request:t})=>{let s=await t.clone().formData(),i=String(s.get("_action")),g=null;return i==="Sign In with Google"?g=await o.authenticate("google",t):g=null,g},d=async t=>await r.logout(t),l=async t=>{let s=await d(t),i=new Headers;return i.append("Set-Cookie",s),(0,G.redirect)(y,{headers:i})};return{loginLoader:w,loginAction:a,logoutLoader:async({request:t})=>{throw await l(t)},logout:d,logoutAndRedirect:l,googleCallbackLoader:async({request:t})=>{try{let s=await o.authenticate("google",t),p=(await r.getSession(t)).get(r.sessionKey)?.refreshToken,L={accessToken:s.accessToken,refreshToken:s.refreshToken||p,expirationDateMs:s.expirationDateMs,provider:s.provider},S={id:s.id,displayName:s.displayName,name:s.name,emails:s.emails,...L,role:s.role},F=await r.saveSession(t,S);throw(0,G.redirect)(m,{headers:{"Set-Cookie":F}})}catch(s){throw s instanceof Response?s:(u.error("Authentication error:",s),(0,G.redirect)(n))}}}};var _=require("react-router"),O=(e,r)=>{if(!e.secrets||e.secrets.length===0||e.secrets.some(i=>!i))throw new Error("REMIX_SESSION_SECRET must be set");let o=e.sessionKey??"user",u=e.maxAge??3600*24*30,n=r?.loginPath??"/auth/login",m=r?.postLoginRedirect??"/",y=r?.mockUser,w=r?.isUserActive,a=(0,_.createCookieSessionStorage)({cookie:{name:e.cookieName??"__session",httpOnly:!0,path:"/",sameSite:"lax",secrets:e.secrets,secure:e.secure??process.env.NODE_ENV==="production",maxAge:u}}),d=async i=>await a.getSession(i.headers.get("Cookie")),l=async i=>{if(y?.enabled(i))return y.resolve(i);let p=(await d(i)).get(o);if(p!==void 0&&!(w&&!await w(p)))return p},h=async(i,g)=>{let p=await d(i);return p.set(o,g),await a.commitSession(p)},c=async i=>{let g=await l(i);if(g===void 0)throw(0,_.redirect)(n);return g};return{sessionStorage:a,sessionKey:o,getSession:d,getSessionUser:l,saveSession:h,requireUser:c,requireAdminUser:async i=>{let g=await c(i);if(g?.role!=="admin")throw(0,_.redirect)(m);return g},logout:async i=>{let g=await d(i);return await a.destroySession(g)},commitSession:a.commitSession,destroySession:a.destroySession}};var z=e=>{let r=e.logger??N,o=O(e.session,{mockUser:e.mockUser,loginPath:e.loginPath,postLoginRedirect:e.postLoginRedirect,isUserActive:e.isUserActive}),u=q(e),n=b(e,o,r),m=I(e,o,n,r),y=D(e,o,u,r);return{authenticator:u,storage:o,authFns:n,sessionStorage:o.sessionStorage,getSession:o.getSession,getSessionUser:o.getSessionUser,saveSession:o.saveSession,requireUser:o.requireUser,requireAdminUser:o.requireAdminUser,commitSession:o.commitSession,destroySession:o.destroySession,authenticate:n.authenticate,refreshedAccessToken:n.refreshedAccessToken,authenticateInLoader:m,...y}};0&&(module.exports={GoogleInvalidGrantError,TokenRefreshError,createAuthHandlers,createAuthServer,createAuthSessionStorage,createAuthenticate,createAuthenticateInLoader,createGoogleAuthenticator});
|
|
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 S=typeof h=="object"&&h&&"error_description"in h?String(h.error_description):"Unknown error";throw new w(`Google token refresh failed: ${t.status} ${S}`)}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},k=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=k.get(a);if(l!==void 0)return l;let t=d(a);return k.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:k,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=k??!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:k})=>{let{access_token:d,refresh_token:p,expires_in:g,scope:a,id_token:l}=k.data,{_json:{sub:t,email:o,picture:n,hd:c,email_verified:h},photos:x,...S}=await L.userProfile(k),{id:T,displayName:A,name:{familyName:ne,givenName:ie},emails:ae}=S,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: ",S.displayName);try{if(e.backendAuth){if(!await e.backendAuth.verify(j,d)){s.error("Backend verification returned false for user:",S.displayName);let v=encodeURIComponent(r.backendFailed);throw(0,M.redirect)(`${u}?error=${v}`)}s.info("Authenticated on Backend: ",S.displayName)}let f=S.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(S)){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),s.info("Authenticated complete: ",S.displayName);let U=Date.now()+g*1e3;return{...S,...{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??"/",k=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:k,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},S={id:o.id,displayName:o.displayName,name:o.name,emails:o.emails,...x,role:o.role},T=await s.saveSession(t,S);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,k=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&&!(k&&!await k(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
2
|
//# sourceMappingURL=server.js.map
|