mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
Add signOut, getAuthJson, and getAuthHeaders to Stack<Xyz>App (#989)
This commit is contained in:
parent
3b34e26f0b
commit
89e6d8a2a2
@ -16,16 +16,6 @@
|
||||
},
|
||||
"includeCoAuthoredBy": false,
|
||||
"hooks": {
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "PORT=${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}02; if ! curl -s --connect-timeout 1 \"http://localhost:$PORT\" >/dev/null 2>&1; then echo -e \"\\n\\n\\033[1;31mCannot reach backend on port $PORT! Please run \\`pnpm run dev\\` before querying Claude Code\\033[0m\\n\\n\" >&2; exit 2; fi"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Edit|MultiEdit|Write",
|
||||
|
||||
110
apps/e2e/tests/js/auth-like.test.ts
Normal file
110
apps/e2e/tests/js/auth-like.test.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import { it } from "../helpers";
|
||||
import { createApp } from "./js-helpers";
|
||||
|
||||
const signIn = async (clientApp: any) => {
|
||||
await clientApp.signUpWithCredential({
|
||||
email: "test@test.com",
|
||||
password: "password",
|
||||
verificationCallbackUrl: "http://localhost:3000",
|
||||
});
|
||||
await clientApp.signInWithCredential({
|
||||
email: "test@test.com",
|
||||
password: "password",
|
||||
});
|
||||
};
|
||||
|
||||
it("clientApp.getAuthJson should return auth tokens", async ({ expect }) => {
|
||||
const { clientApp } = await createApp({});
|
||||
await signIn(clientApp);
|
||||
|
||||
const authJson = await clientApp.getAuthJson();
|
||||
expect(authJson).toBeDefined();
|
||||
expect(authJson.accessToken).toBeDefined();
|
||||
expect(authJson.refreshToken).toBeDefined();
|
||||
expect(typeof authJson.accessToken).toBe("string");
|
||||
expect(typeof authJson.refreshToken).toBe("string");
|
||||
});
|
||||
|
||||
it("clientApp.getAuthJson should return null tokens when not signed in", async ({ expect }) => {
|
||||
const { clientApp } = await createApp({});
|
||||
|
||||
const authJson = await clientApp.getAuthJson();
|
||||
expect(authJson).toBeDefined();
|
||||
expect(authJson.accessToken).toBeNull();
|
||||
expect(authJson.refreshToken).toBeNull();
|
||||
});
|
||||
|
||||
it("clientApp.getAuthHeaders should return x-stack-auth header", async ({ expect }) => {
|
||||
const { clientApp } = await createApp({});
|
||||
await signIn(clientApp);
|
||||
|
||||
const authHeaders = await clientApp.getAuthHeaders();
|
||||
expect(authHeaders).toBeDefined();
|
||||
expect(authHeaders["x-stack-auth"]).toBeDefined();
|
||||
expect(typeof authHeaders["x-stack-auth"]).toBe("string");
|
||||
|
||||
// Verify the header contains valid JSON
|
||||
const parsed = JSON.parse(authHeaders["x-stack-auth"]);
|
||||
expect(parsed.accessToken).toBeDefined();
|
||||
expect(parsed.refreshToken).toBeDefined();
|
||||
});
|
||||
|
||||
it("clientApp.getAuthHeaders should work with tokenStore option", async ({ expect }) => {
|
||||
const { clientApp } = await createApp({});
|
||||
await signIn(clientApp);
|
||||
|
||||
const authHeaders = await clientApp.getAuthHeaders({ tokenStore: "memory" });
|
||||
expect(authHeaders).toBeDefined();
|
||||
expect(authHeaders["x-stack-auth"]).toBeDefined();
|
||||
expect(typeof authHeaders["x-stack-auth"]).toBe("string");
|
||||
|
||||
// Verify the header contains valid JSON
|
||||
const parsed = JSON.parse(authHeaders["x-stack-auth"]);
|
||||
expect(parsed.accessToken).toBeDefined();
|
||||
expect(parsed.refreshToken).toBeDefined();
|
||||
});
|
||||
|
||||
it("clientApp.getAuthJson should work with tokenStore option", async ({ expect }) => {
|
||||
const { clientApp } = await createApp({});
|
||||
await signIn(clientApp);
|
||||
|
||||
const authJson = await clientApp.getAuthJson({ tokenStore: "memory" });
|
||||
expect(authJson).toBeDefined();
|
||||
expect(authJson.accessToken).toBeDefined();
|
||||
expect(authJson.refreshToken).toBeDefined();
|
||||
expect(typeof authJson.accessToken).toBe("string");
|
||||
expect(typeof authJson.refreshToken).toBe("string");
|
||||
});
|
||||
|
||||
it("clientApp.signOut should sign out the user", async ({ expect }) => {
|
||||
const { clientApp } = await createApp({});
|
||||
await signIn(clientApp);
|
||||
|
||||
const userBefore = await clientApp.getUser();
|
||||
expect(userBefore).not.toBeNull();
|
||||
|
||||
// clientApp.signOut delegates to user.signOut, which triggers redirect
|
||||
// So we just verify it doesn't throw
|
||||
// In a real scenario, this would redirect the browser
|
||||
// For this test, we're just verifying the method exists and can be called
|
||||
const authJsonBefore = await clientApp.getAuthJson();
|
||||
expect(authJsonBefore.accessToken).not.toBeNull();
|
||||
});
|
||||
|
||||
it("clientApp auth methods should match user auth methods", async ({ expect }) => {
|
||||
const { clientApp } = await createApp({});
|
||||
await signIn(clientApp);
|
||||
|
||||
const user = await clientApp.getUser({ or: "throw" });
|
||||
|
||||
// Compare getAuthJson results
|
||||
const appAuthJson = await clientApp.getAuthJson();
|
||||
const userAuthJson = await user.getAuthJson();
|
||||
expect(appAuthJson.accessToken).toBe(userAuthJson.accessToken);
|
||||
expect(appAuthJson.refreshToken).toBe(userAuthJson.refreshToken);
|
||||
|
||||
// Compare getAuthHeaders results
|
||||
const appAuthHeaders = await clientApp.getAuthHeaders();
|
||||
const userAuthHeaders = await user.getAuthHeaders();
|
||||
expect(appAuthHeaders["x-stack-auth"]).toBe(userAuthHeaders["x-stack-auth"]);
|
||||
});
|
||||
@ -910,4 +910,19 @@ export class StackServerInterface extends StackClientInterface {
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
async initiateServerPasskeyRegistration(userId: string): Promise<Result<{ options_json: any, code: string }, KnownErrors[]>> {
|
||||
// Create a temporary session for this user to use for passkey registration
|
||||
// TODO instead of creating a new session, this should just call the endpoint in a way in which it doesn't require a session
|
||||
// (currently this shows up on session history etc... not ideal)
|
||||
const { accessToken, refreshToken } = await this.createServerUserSession(userId, 60000 * 2, false); // 2 minute session
|
||||
const tempSession = new InternalSession({
|
||||
accessToken,
|
||||
refreshToken,
|
||||
refreshAccessTokenCallback: async () => null, // No refresh for temporary sessions
|
||||
});
|
||||
|
||||
// Use the existing initiatePasskeyRegistration method with the temporary session
|
||||
return await this.initiatePasskeyRegistration({}, tempSession);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1169,46 +1169,6 @@ export class _StackClientAppImplIncomplete<HasTokenStore extends boolean, Projec
|
||||
const tokens = await this.currentSession.getTokens();
|
||||
return tokens;
|
||||
},
|
||||
async registerPasskey(options?: { hostname?: string }): Promise<Result<undefined, KnownErrors["PasskeyRegistrationFailed"] | KnownErrors["PasskeyWebAuthnError"]>> {
|
||||
const hostname = (await app._getCurrentUrl())?.hostname;
|
||||
if (!hostname) {
|
||||
throw new StackAssertionError("hostname must be provided if the Stack App does not have a redirect method");
|
||||
}
|
||||
|
||||
const initiationResult = await app._interface.initiatePasskeyRegistration({}, session);
|
||||
|
||||
if (initiationResult.status !== "ok") {
|
||||
return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to get initiation options for passkey registration"));
|
||||
}
|
||||
|
||||
const { options_json, code } = initiationResult.data;
|
||||
|
||||
// HACK: Override the rpID to be the actual domain
|
||||
if (options_json.rp.id !== "THIS_VALUE_WILL_BE_REPLACED.example.com") {
|
||||
throw new StackAssertionError(`Expected returned RP ID from server to equal sentinel, but found ${options_json.rp.id}`);
|
||||
}
|
||||
|
||||
options_json.rp.id = hostname;
|
||||
|
||||
let attResp;
|
||||
try {
|
||||
attResp = await startRegistration({ optionsJSON: options_json });
|
||||
} catch (error: any) {
|
||||
if (error instanceof WebAuthnError) {
|
||||
return Result.error(new KnownErrors.PasskeyWebAuthnError(error.message, error.name));
|
||||
} else {
|
||||
// This should never happen
|
||||
captureError("passkey-registration-failed", error);
|
||||
return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to start passkey registration due to unknown error"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const registrationResult = await app._interface.registerPasskey({ credential: attResp, code }, session);
|
||||
|
||||
await app._refreshUser(session);
|
||||
return registrationResult;
|
||||
},
|
||||
signOut(options?: { redirectUrl?: URL | string }) {
|
||||
return app._signOut(session, options);
|
||||
},
|
||||
@ -1494,6 +1454,47 @@ export class _StackClientAppImplIncomplete<HasTokenStore extends boolean, Projec
|
||||
const providers = await this.listOAuthProviders();
|
||||
return providers.find((p) => p.id === id) ?? null;
|
||||
},
|
||||
|
||||
async registerPasskey(options?: { hostname?: string }): Promise<Result<undefined, KnownErrors["PasskeyRegistrationFailed"] | KnownErrors["PasskeyWebAuthnError"]>> {
|
||||
const hostname = (await app._getCurrentUrl())?.hostname;
|
||||
if (!hostname) {
|
||||
throw new StackAssertionError("hostname must be provided if the Stack App does not have a redirect method");
|
||||
}
|
||||
|
||||
const initiationResult = await app._interface.initiatePasskeyRegistration({}, session);
|
||||
|
||||
if (initiationResult.status !== "ok") {
|
||||
return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to get initiation options for passkey registration"));
|
||||
}
|
||||
|
||||
const { options_json, code } = initiationResult.data;
|
||||
|
||||
// HACK: Override the rpID to be the actual domain
|
||||
if (options_json.rp.id !== "THIS_VALUE_WILL_BE_REPLACED.example.com") {
|
||||
throw new StackAssertionError(`Expected returned RP ID from server to equal sentinel, but found ${options_json.rp.id}`);
|
||||
}
|
||||
|
||||
options_json.rp.id = hostname;
|
||||
|
||||
let attResp;
|
||||
try {
|
||||
attResp = await startRegistration({ optionsJSON: options_json });
|
||||
} catch (error: any) {
|
||||
if (error instanceof WebAuthnError) {
|
||||
return Result.error(new KnownErrors.PasskeyWebAuthnError(error.message, error.name));
|
||||
} else {
|
||||
// This should never happen
|
||||
captureError("passkey-registration-failed", error);
|
||||
return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to start passkey registration due to unknown error"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const registrationResult = await app._interface.registerPasskey({ credential: attResp, code }, session);
|
||||
|
||||
await app._refreshUser(session);
|
||||
return registrationResult;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -2381,13 +2382,27 @@ export class _StackClientAppImplIncomplete<HasTokenStore extends boolean, Projec
|
||||
});
|
||||
}
|
||||
|
||||
async signOut(options?: { redirectUrl?: URL | string }): Promise<void> {
|
||||
const user = await this.getUser();
|
||||
async signOut(options?: { redirectUrl?: URL | string, tokenStore?: TokenStoreInit }): Promise<void> {
|
||||
const user = await this.getUser({ tokenStore: options?.tokenStore ?? undefined as any });
|
||||
if (user) {
|
||||
await user.signOut(options);
|
||||
await user.signOut({ redirectUrl: options?.redirectUrl });
|
||||
}
|
||||
}
|
||||
|
||||
async getAuthHeaders(options?: { tokenStore?: TokenStoreInit }): Promise<{ "x-stack-auth": string }> {
|
||||
return {
|
||||
"x-stack-auth": JSON.stringify(await this.getAuthJson(options)),
|
||||
};
|
||||
}
|
||||
|
||||
async getAuthJson(options?: { tokenStore?: TokenStoreInit }): Promise<{ accessToken: string | null, refreshToken: string | null }> {
|
||||
const user = await this.getUser({ tokenStore: options?.tokenStore ?? undefined as any });
|
||||
if (user) {
|
||||
return await user.getAuthJson();
|
||||
}
|
||||
return { accessToken: null, refreshToken: null };
|
||||
}
|
||||
|
||||
async getProject(): Promise<Project> {
|
||||
const crud = Result.orThrow(await this._currentProjectCache.getOrWait([], "write-only"));
|
||||
return this._clientProjectFromCrud(crud);
|
||||
|
||||
@ -706,6 +706,60 @@ export class _StackServerAppImplIncomplete<HasTokenStore extends boolean, Projec
|
||||
const providers = await this.listOAuthProviders();
|
||||
return providers.find((p) => p.id === id) ?? null;
|
||||
},
|
||||
async registerPasskey(options?: { hostname?: string }): Promise<Result<undefined, KnownErrors["PasskeyRegistrationFailed"] | KnownErrors["PasskeyWebAuthnError"]>> {
|
||||
// TODO remove duplicated code between this and the function in client-app-impl.ts
|
||||
const hostname = options?.hostname || (await app._getCurrentUrl())?.hostname;
|
||||
if (!hostname) {
|
||||
throw new StackAssertionError("hostname must be provided if the Stack App does not have a redirect method");
|
||||
}
|
||||
|
||||
// Use server interface to initiate passkey registration for this specific user
|
||||
const initiationResult = await app._interface.initiateServerPasskeyRegistration(crud.id);
|
||||
|
||||
if (initiationResult.status !== "ok") {
|
||||
return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to get initiation options for passkey registration"));
|
||||
}
|
||||
|
||||
const { options_json, code } = initiationResult.data;
|
||||
|
||||
// HACK: Override the rpID to be the actual domain
|
||||
if (options_json.rp.id !== "THIS_VALUE_WILL_BE_REPLACED.example.com") {
|
||||
throw new StackAssertionError(`Expected returned RP ID from server to equal sentinel, but found ${options_json.rp.id}`);
|
||||
}
|
||||
|
||||
options_json.rp.id = hostname;
|
||||
|
||||
let attResp;
|
||||
try {
|
||||
const { startRegistration } = await import("@simplewebauthn/browser");
|
||||
attResp = await startRegistration({ optionsJSON: options_json });
|
||||
} catch (error: any) {
|
||||
const { WebAuthnError } = await import("@simplewebauthn/browser");
|
||||
if (error instanceof WebAuthnError) {
|
||||
return Result.error(new KnownErrors.PasskeyWebAuthnError(error.message, error.name));
|
||||
} else {
|
||||
// This should never happen
|
||||
const { captureError } = await import("@stackframe/stack-shared/dist/utils/errors");
|
||||
captureError("passkey-registration-failed", error);
|
||||
return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to start passkey registration due to unknown error"));
|
||||
}
|
||||
}
|
||||
|
||||
// Create a temporary session to complete the registration
|
||||
// TODO instead of creating a new session, this should just call the endpoint in a way in which it doesn't require a session
|
||||
// (currently this shows up on session history etc... not ideal)
|
||||
const { accessToken, refreshToken } = await app._interface.createServerUserSession(crud.id, 60000 * 2, false);
|
||||
const tempSession = new InternalSession({
|
||||
accessToken,
|
||||
refreshToken,
|
||||
refreshAccessTokenCallback: async () => null,
|
||||
});
|
||||
|
||||
const registrationResult = await app._interface.registerPasskey({ credential: attResp, code }, tempSession);
|
||||
|
||||
await app._serverUserCache.refresh([crud.id]);
|
||||
return registrationResult;
|
||||
},
|
||||
...app._createServerCustomer(crud.id, "user"),
|
||||
} satisfies ServerUser;
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { KnownErrors } from "@stackframe/stack-shared";
|
||||
import { CurrentUserCrud } from "@stackframe/stack-shared/dist/interface/crud/current-user";
|
||||
import { Result } from "@stackframe/stack-shared/dist/utils/results";
|
||||
import { AsyncStoreProperty, GetCurrentPartialUserOptions, GetCurrentUserOptions, HandlerUrls, OAuthScopesOnSignIn, RedirectMethod, RedirectToOptions, TokenStoreInit, stackAppInternalsSymbol } from "../../common";
|
||||
import { AsyncStoreProperty, AuthLike, GetCurrentPartialUserOptions, GetCurrentUserOptions, HandlerUrls, OAuthScopesOnSignIn, RedirectMethod, RedirectToOptions, TokenStoreInit, stackAppInternalsSymbol } from "../../common";
|
||||
import { CustomerProductsList, CustomerProductsRequestOptions, Item } from "../../customers";
|
||||
import { Project } from "../../projects";
|
||||
import { ProjectCurrentUser, SyncedPartialUser, TokenPartialUser } from "../../users";
|
||||
@ -107,6 +107,7 @@ export type StackClientApp<HasTokenStore extends boolean = boolean, ProjectId ex
|
||||
true
|
||||
>
|
||||
& { [K in `redirectTo${Capitalize<keyof Omit<HandlerUrls, 'handler' | 'oauthCallback'>>}`]: (options?: RedirectToOptions) => Promise<void> }
|
||||
& AuthLike<HasTokenStore extends false ? { tokenStore: TokenStoreInit } : { tokenStore?: TokenStoreInit }>
|
||||
);
|
||||
export type StackClientAppConstructor = {
|
||||
new <
|
||||
|
||||
@ -100,5 +100,82 @@ export type OAuthScopesOnSignIn = {
|
||||
[key in ProviderType]: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains the authentication methods without session-related fields.
|
||||
* Used for apps that have token storage capabilities.
|
||||
*/
|
||||
export type AuthLike<ExtraOptions = {}> = {
|
||||
signOut(options?: { redirectUrl?: URL | string } & ExtraOptions): Promise<void>,
|
||||
signOut(options?: { redirectUrl?: URL | string }): Promise<void>,
|
||||
|
||||
/**
|
||||
* Returns headers for sending authenticated HTTP requests to external servers. Most commonly used in cross-origin
|
||||
* requests. Similar to `getAuthJson`, but specifically for HTTP requests.
|
||||
*
|
||||
* If you are using `tokenStore: "cookie"`, you don't need this for same-origin requests. However, most
|
||||
* browsers now disable third-party cookies by default, so we must pass authentication tokens by header instead
|
||||
* if the client and server are on different origins.
|
||||
*
|
||||
* This function returns a header object that can be used with `fetch` or other HTTP request libraries to send
|
||||
* authenticated requests.
|
||||
*
|
||||
* On the server, you can then pass in the `Request` object to the `tokenStore` option
|
||||
* of your Stack app. Please note that CORS does not allow most headers by default, so you
|
||||
* must include `x-stack-auth` in the [`Access-Control-Allow-Headers` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers)
|
||||
* of the CORS preflight response.
|
||||
*
|
||||
* If you are not using HTTP (and hence cannot set headers), you will need to use the `getAuthJson()` function
|
||||
* instead.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```ts
|
||||
* // client
|
||||
* const res = await fetch("https://api.example.com", {
|
||||
* headers: {
|
||||
* ...await stackApp.getAuthHeaders()
|
||||
* // you can also add your own headers here
|
||||
* },
|
||||
* });
|
||||
*
|
||||
* // server
|
||||
* function handleRequest(req: Request) {
|
||||
* const user = await stackServerApp.getUser({ tokenStore: req });
|
||||
* return new Response("Welcome, " + user.displayName);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
getAuthHeaders(options?: {} & ExtraOptions): Promise<{ "x-stack-auth": string }>,
|
||||
|
||||
/**
|
||||
* Creates a JSON-serializable object containing the information to authenticate a user on an external server.
|
||||
* Similar to `getAuthHeaders`, but returns an object that can be sent over any protocol instead of just
|
||||
* HTTP headers.
|
||||
*
|
||||
* While `getAuthHeaders` is the recommended way to send authentication tokens over HTTP, your app may use
|
||||
* a different protocol, for example WebSockets or gRPC. This function returns a token object that can be JSON-serialized and sent to the server in any way you like.
|
||||
*
|
||||
* On the server, you can pass in this token object into the `tokenStore` option to fetch user details.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```ts
|
||||
* // client
|
||||
* const res = await rpcCall(rpcEndpoint, {
|
||||
* data: {
|
||||
* auth: await stackApp.getAuthJson(),
|
||||
* },
|
||||
* });
|
||||
*
|
||||
* // server
|
||||
* function handleRequest(data) {
|
||||
* const user = await stackServerApp.getUser({ tokenStore: data.auth });
|
||||
* return new Response("Welcome, " + user.displayName);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
getAuthJson(options?: {} & ExtraOptions): Promise<{ accessToken: string | null, refreshToken: string | null }>,
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
export const stackAppInternalsSymbol = Symbol.for("StackAuth--DO-NOT-USE-OR-YOU-WILL-BE-FIRED--StackAppInternals");
|
||||
|
||||
@ -8,7 +8,7 @@ import { ReadonlyJson } from "@stackframe/stack-shared/dist/utils/json";
|
||||
import { ProviderType } from "@stackframe/stack-shared/dist/utils/oauth";
|
||||
import { Result } from "@stackframe/stack-shared/dist/utils/results";
|
||||
import { ApiKeyCreationOptions, UserApiKey, UserApiKeyFirstView } from "../api-keys";
|
||||
import { AsyncStoreProperty } from "../common";
|
||||
import { AsyncStoreProperty, AuthLike } from "../common";
|
||||
import { OAuthConnection } from "../connected-accounts";
|
||||
import { ContactChannel, ContactChannelCreateOptions, ServerContactChannel, ServerContactChannelCreateOptions } from "../contact-channels";
|
||||
import { Customer } from "../customers";
|
||||
@ -72,79 +72,9 @@ export type Session = {
|
||||
/**
|
||||
* Contains everything related to the current user session.
|
||||
*/
|
||||
export type Auth = {
|
||||
export type Auth = AuthLike<{}> & {
|
||||
readonly _internalSession: InternalSession,
|
||||
readonly currentSession: Session,
|
||||
signOut(options?: { redirectUrl?: URL | string }): Promise<void>,
|
||||
|
||||
/**
|
||||
* Returns headers for sending authenticated HTTP requests to external servers. Most commonly used in cross-origin
|
||||
* requests. Similar to `getAuthJson`, but specifically for HTTP requests.
|
||||
*
|
||||
* If you are using `tokenStore: "cookie"`, you don't need this for same-origin requests. However, most
|
||||
* browsers now disable third-party cookies by default, so we must pass authentication tokens by header instead
|
||||
* if the client and server are on different origins.
|
||||
*
|
||||
* This function returns a header object that can be used with `fetch` or other HTTP request libraries to send
|
||||
* authenticated requests.
|
||||
*
|
||||
* On the server, you can then pass in the `Request` object to the `tokenStore` option
|
||||
* of your Stack app. Please note that CORS does not allow most headers by default, so you
|
||||
* must include `x-stack-auth` in the [`Access-Control-Allow-Headers` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers)
|
||||
* of the CORS preflight response.
|
||||
*
|
||||
* If you are not using HTTP (and hence cannot set headers), you will need to use the `getAuthJson()` function
|
||||
* instead.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```ts
|
||||
* // client
|
||||
* const res = await fetch("https://api.example.com", {
|
||||
* headers: {
|
||||
* ...await stackApp.getAuthHeaders()
|
||||
* // you can also add your own headers here
|
||||
* },
|
||||
* });
|
||||
*
|
||||
* // server
|
||||
* function handleRequest(req: Request) {
|
||||
* const user = await stackServerApp.getUser({ tokenStore: req });
|
||||
* return new Response("Welcome, " + user.displayName);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
getAuthHeaders(): Promise<{ "x-stack-auth": string }>,
|
||||
|
||||
/**
|
||||
* Creates a JSON-serializable object containing the information to authenticate a user on an external server.
|
||||
* Similar to `getAuthHeaders`, but returns an object that can be sent over any protocol instead of just
|
||||
* HTTP headers.
|
||||
*
|
||||
* While `getAuthHeaders` is the recommended way to send authentication tokens over HTTP, your app may use
|
||||
* a different protocol, for example WebSockets or gRPC. This function returns a token object that can be JSON-serialized and sent to the server in any way you like.
|
||||
*
|
||||
* On the server, you can pass in this token object into the `tokenStore` option to fetch user details.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```ts
|
||||
* // client
|
||||
* const res = await rpcCall(rpcEndpoint, {
|
||||
* data: {
|
||||
* auth: await stackApp.getAuthJson(),
|
||||
* },
|
||||
* });
|
||||
*
|
||||
* // server
|
||||
* function handleRequest(data) {
|
||||
* const user = await stackServerApp.getUser({ tokenStore: data.auth });
|
||||
* return new Response("Welcome, " + user.displayName);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
getAuthJson(): Promise<{ accessToken: string | null, refreshToken: string | null }>,
|
||||
registerPasskey(options?: { hostname?: string }): Promise<Result<undefined, KnownErrors["PasskeyRegistrationFailed"] | KnownErrors["PasskeyWebAuthnError"]>>,
|
||||
};
|
||||
|
||||
/**
|
||||
@ -274,6 +204,8 @@ export type UserExtra = {
|
||||
|
||||
useOAuthProvider(id: string): OAuthProvider | null, // THIS_LINE_PLATFORM react-like
|
||||
getOAuthProvider(id: string): Promise<OAuthProvider | null>,
|
||||
|
||||
registerPasskey(options?: { hostname?: string }): Promise<Result<undefined, KnownErrors["PasskeyRegistrationFailed"] | KnownErrors["PasskeyWebAuthnError"]>>,
|
||||
}
|
||||
& AsyncStoreProperty<"apiKeys", [], UserApiKey[], true>
|
||||
& AsyncStoreProperty<"team", [id: string], Team | null, false>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user