Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 40 additions & 61 deletions packages/core/src/@types/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Session, User } from "@/@types/session.ts"
import type { AuthResponse, DeepPartial, Prettify } from "@/@types/utility.ts"
import type { AuthResponse, DeepPartial, Prettify, RequiredKeys } from "@/@types/utility.ts"
import type { CredentialsPayload, RouterGlobalContext } from "@/@types/config.ts"

/**
Expand Down Expand Up @@ -182,9 +182,20 @@ export interface SignInCredentialsOptions extends OptionsWithRedirectTo {
payload: CredentialsPayload
}

export type SignInCredentialsReturnData =
/** redirect: true & redirectTo: string */
| { success: true; redirect: true; redirectURL: null }
/** redirect: false & redirectTo: string */
| { success: true; redirect: false; redirectURL: string }
/** redirect: false & redirectTo: null | undefined (not set) */
/** redirect: true & redirectTo: null | undefined (not set) */
| { success: true; redirect: false; redirectURL: null }
/** Failed credentials */
| { success: false; redirect: false; redirectURL: null }

/** Client-side credentials sign-in return type (redirect mode or manual redirect data). */
export type SignInCredentialsReturn<Options extends SignInCredentialsOptions> = Options extends { redirect: false }
? { success: true; redirectURL: string } | { success: false; redirectURL: null }
? Extract<SignInCredentialsReturnData, { redirect: false }>
: void

/** Server/programmatic credentials sign-in options. */
Expand All @@ -201,85 +212,62 @@ export interface SignInCredentialsAPIOptions extends APIOptionsWithRedirectTo, A
}

/** Programmatic credentials sign-in result with response metadata and `toResponse()`. */
export type SignInCredentialsAPIReturn = AuthActionAPIReturn<
export type SignInCredentialsAPIReturn = AuthActionAPIReturn<SignInCredentialsReturnData>

/** Client-side sign-out options. */
export interface SignOutOptions extends OptionsWithRedirectTo {}

export type SignOutReturnData =
/** redirect: true & redirectTo: string */
| { success: true; redirect: true; redirectURL: null }
/** redirect: false & redirectTo: string */
| { success: true; redirect: false; redirectURL: string }
/** redirect: false & redirectTo: null | undefined (not set) */
/** redirect: true & redirectTo: null | undefined (not set) */
| { success: true; redirect: false; redirectURL: null }
/** Failed credentials */
/** Failed */
| { success: false; redirect: false; redirectURL: null }
>

/** Client-side sign-out options. */
export interface SignOutOptions extends OptionsWithRedirectTo {}

/** Client-side sign-out return type (redirect mode or manual redirect data). */
export type SignOutReturn<Options extends SignOutOptions> = Options extends { redirect: false }
? { success: true; redirect: false; redirectURL: string } | { success: false; redirect: false; redirectURL: null }
? Extract<SignOutReturnData, { redirect: false }>
: void

/** Server/programmatic options for `signOut` API. */
export interface SignOutAPIOptions extends APIOptionsWithRedirectTo, APIOptionsWithSkipCSRFCheck {
/**
* Required headers used to execute sign-out.
* Must include `session_token` and `csrf_token` cookies for CSRF validation.
* @example
* {
* Cookie: "session_token=abc123; csrf_token=def456"
* }
*/
headers: HeadersInit
/**
* Optional `Request` object as an alternative to manually providing `headers`.
*/
request?: Request
}
export interface SignOutAPIOptions extends RequiredKeys<APIOptionsWithRequest, "headers">, APIOptionsWithSkipCSRFCheck {}

/** Programmatic sign-out result with redirect metadata and `toResponse()`. */
export type SignOutAPIReturn = AuthActionAPIReturn<
/** redirect: true & redirectTo: string */
| { success: true; redirect: true; redirectURL: null }
/** redirect: false & redirectTo: string */
| { success: true; redirect: false; redirectURL: string }
/** redirect: false & redirectTo: null | undefined (not set) */
/** redirect: true & redirectTo: null | undefined (not set) */
| { success: true; redirect: false; redirectURL: null }
/** Failed */
| { success: false; redirect: false; redirectURL: null }
>
export type SignOutAPIReturn = AuthActionAPIReturn<SignOutReturnData>

/** Client-side `updateSession` options: partial session payload plus optional redirect behavior. */
export interface UpdateSessionOptions<DefaultUser extends User = User> extends OptionsWithRedirectTo {
/** Partial session data to merge into the current session. */
session: DeepPartial<Session<DefaultUser>>
}

export type UpdateSessionReturnData<DefaultUser extends User = User> =
/** redirect: true & redirectTo: string */
| { success: true; session: Session<DefaultUser>; redirect: true; redirectURL: null }
/** redirect: false & redirectTo: string */
| { success: true; session: Session<DefaultUser>; redirect: false; redirectURL: string }
/** redirect: false & redirectTo: null | undefined (not set) */
| { success: true; session: Session<DefaultUser>; redirect: false; redirectURL: null }
/** Failed session update */
| { success: false; session: null; redirect: false; redirectURL: null }

/** Client-side `updateSession` return type. */
export type UpdateSessionReturn<Options extends UpdateSessionOptions, DefaultUser extends User = User> = Options extends {
export type UpdateSessionReturn<
Options extends UpdateSessionOptions<DefaultUser>,
DefaultUser extends User = User,
> = Options extends {
redirect: false
}
? { success: true; session: Session<DefaultUser> } | { success: false; session: null }
? Extract<UpdateSessionReturnData<DefaultUser>, { redirect: false }>
: void

/** Server/programmatic options for `updateSession` API. */
export interface UpdateSessionAPIOptions<DefaultUser extends User = User>
extends APIOptionsWithRequest, APIOptionsWithSkipCSRFCheck {
/**
* Required headers used to execute session update.
* Must include `session_token` and `csrf_token` cookies for CSRF validation.
* @example
* {
* Cookie: "session_token=abc123; csrf_token=def456"
* }
*/
headers: HeadersInit
/**
* Optional `Request` object as an alternative to manually providing `headers`.
*/
request?: Request
extends RequiredKeys<APIOptionsWithRequest, "headers">, APIOptionsWithSkipCSRFCheck {
/**
* Partial session payload used to update the current session.
* @see Session
Expand All @@ -295,13 +283,4 @@ export interface UpdateSessionAPIOptions<DefaultUser extends User = User>
}

/** Programmatic session update result with redirect metadata and `toResponse()`. */
export type UpdateSessionAPIReturn<DefaultUser extends User = User> = AuthActionAPIReturn<
/** redirect: true & redirectTo: string */
| { success: true; session: Session<DefaultUser>; redirect: true; redirectURL: null }
/** redirect: false & redirectTo: string */
| { success: true; session: Session<DefaultUser>; redirect: false; redirectURL: string }
/** redirect: false & redirectTo: null | undefined (not set) */
| { success: true; session: Session<DefaultUser>; redirect: false; redirectURL: null }
/** Failed session update */
| { success: false; session: null; redirect: false; redirectURL: null }
>
export type UpdateSessionAPIReturn<DefaultUser extends User = User> = AuthActionAPIReturn<UpdateSessionReturnData<DefaultUser>>
6 changes: 6 additions & 0 deletions packages/core/src/@types/utility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,9 @@ export type AuthResponse<Body = unknown> = Prettify<
json(): Promise<Body>
}
>

export type RequiredKeys<Obj extends object, Keys extends keyof Obj = keyof Obj> = Wrap<
{
[K in Keys]-?: Obj[K]
} & Omit<Obj, Keys>
>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createEndpoint, createEndpointConfig } from "@aura-stack/router"
import { signInCredentials } from "@/api/credentials.ts"
import { RedirectOptionsSchema, CredentialsPayloadSchema } from "@/schemas"
import { RedirectOptionsSchema, CredentialsPayloadSchema } from "@/schemas.ts"

const config = createEndpointConfig({
schemas: {
Expand Down
14 changes: 11 additions & 3 deletions packages/core/src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,10 @@ export const createAuthClient = <DefaultUser extends User = User>(options: AuthC
try {
const response = await client.post("/signIn/credentials", {
body: options.payload,
// @ts-ignore - Fix type here - go to @aura-stack/router.
searchParams: {
redirectTo: options?.redirectTo,
...options,
redirect: false,
},
})
const json = await response.json()
Expand Down Expand Up @@ -118,11 +120,15 @@ export const createAuthClient = <DefaultUser extends User = User>(options: AuthC
}
const user = session.user ?? {}
const response = await client.patch("/session", {
// @ts-ignore - Fixing the type here - go to @aura-stack/router.
// @ts-ignore - Fix type here - go to @aura-stack/router.
body: {
user,
expires: session.expires ? new Date(session.expires) : undefined,
},
searchParams: {
...options,
redirect: false,
},
headers: {
"X-CSRF-Token": csrfToken,
},
Expand All @@ -145,9 +151,11 @@ export const createAuthClient = <DefaultUser extends User = User>(options: AuthC
throw new AuthClientError("Failed to fetch CSRF token for sign-out.")
}

// @ts-ignore - Fix type here - go to @aura-stack/router.
const response = await client.post("/signOut", {
searchParams: {
redirectTo: options?.redirectTo,
...options,
redirect: false,
token_type_hint: "session_token",
},
headers: {
Expand Down
2 changes: 1 addition & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,4 @@
"@types/react": ">=19.0.0"
},
"packageManager": "pnpm@10.15.0"
}
}
9 changes: 8 additions & 1 deletion packages/react/src/@types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export type AuthStatus = "authenticated" | "unauthenticated" | "pending"
export interface Context<DefaultUser extends User = User> {
session: Session | null
status: AuthStatus
client: AuthClientInstance<DefaultUser>
client: AuthProviderProps<DefaultUser>["client"]
redirect: AuthProviderProps<DefaultUser>["redirect"]
}

/** Props for {@link AuthProvider}: supply the client and optional SSR session to avoid a flash of loading state. */
Expand All @@ -31,6 +32,12 @@ export type AuthProviderProps<DefaultUser extends User = User> = {
* Pass `null` when the server knows there is no session (skip the initial client fetch).
*/
initialSession?: Session<DefaultUser> | null
/**
* Callback for custom client-side redirects. It overrides the default behavior of window.location.assign
*
* @param url The URL to redirect to, as provided by the auth client (e.g. after signIn or signOut).
*/
redirect?: (url: string) => void | Promise<void>
}

/**
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const AuthProvider = <DefaultUser extends User = User>({
initialSession,
children,
client,
redirect,
}: AuthProviderProps<DefaultUser>) => {
const clientRef = useRef<AuthClientInstance<DefaultUser>>(client)
clientRef.current = client
Expand Down Expand Up @@ -93,5 +94,5 @@ export const AuthProvider = <DefaultUser extends User = User>({
}
}, [refreshSession])

return <AuthContext value={{ session, status, client: clientRef.current }}>{children}</AuthContext>
return <AuthContext value={{ session, status, client: clientRef.current, redirect }}>{children}</AuthContext>
}
Loading
Loading