@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 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?, cookie? }`; `failureRedirect: null` = no-redirect mode. Photo lookups are cached with in-flight dedup per factory instance.
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: (request: Request, options: {
179
- failureRedirect: string;
180
- } | {
181
- failureRedirect: null;
182
- }) => Promise<{
183
- user?: _aiquants_auth_core.MyGoogleProfile;
184
- cookie?: string;
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) => (request: Request, options: {
259
- failureRedirect: string;
260
- } | {
261
- failureRedirect: null;
262
- }) => Promise<{
263
- user?: MyGoogleProfile;
264
- cookie?: string;
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: (request: Request, options: {
179
- failureRedirect: string;
180
- } | {
181
- failureRedirect: null;
182
- }) => Promise<{
183
- user?: _aiquants_auth_core.MyGoogleProfile;
184
- cookie?: string;
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) => (request: Request, options: {
259
- failureRedirect: string;
260
- } | {
261
- failureRedirect: null;
262
- }) => Promise<{
263
- user?: MyGoogleProfile;
264
- cookie?: string;
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