release: WRNexusJS 0.4.0
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/oauth",
|
||||
"version": "0.3.6",
|
||||
"version": "0.4.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { randomToken, type OAuthProvider, type OAuthTokens } from "./index.ts";
|
||||
|
||||
export interface OAuthStateRecord {
|
||||
state: string;
|
||||
verifier: string;
|
||||
redirectUri: string;
|
||||
returnTo?: string;
|
||||
expiresAt: number;
|
||||
}
|
||||
export interface OAuthStateStore {
|
||||
set(record: OAuthStateRecord): Promise<void>;
|
||||
consume(state: string): Promise<OAuthStateRecord | null>;
|
||||
}
|
||||
export function memoryOAuthStateStore(now: () => number = Date.now): OAuthStateStore {
|
||||
const records = new Map<string, OAuthStateRecord>();
|
||||
return {
|
||||
async set(record) {
|
||||
records.set(record.state, record);
|
||||
},
|
||||
async consume(state) {
|
||||
const value = records.get(state);
|
||||
records.delete(state);
|
||||
if (!value || value.expiresAt <= now()) return null;
|
||||
return value;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function createOAuthState(
|
||||
store: OAuthStateStore,
|
||||
input: Omit<OAuthStateRecord, "state" | "expiresAt"> & { ttlMs?: number },
|
||||
): Promise<OAuthStateRecord> {
|
||||
const record: OAuthStateRecord = {
|
||||
state: randomToken(),
|
||||
verifier: input.verifier,
|
||||
redirectUri: input.redirectUri,
|
||||
returnTo: input.returnTo,
|
||||
expiresAt: Date.now() + (input.ttlMs ?? 10 * 60 * 1000),
|
||||
};
|
||||
await store.set(record);
|
||||
return record;
|
||||
}
|
||||
|
||||
export async function refreshOAuthTokens(
|
||||
provider: OAuthProvider,
|
||||
refreshToken: string,
|
||||
fetchImpl: typeof fetch = fetch,
|
||||
): Promise<OAuthTokens> {
|
||||
const response = await fetchImpl(provider.tokenUrl, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
|
||||
body: new URLSearchParams({
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: refreshToken,
|
||||
client_id: provider.clientId,
|
||||
client_secret: provider.clientSecret,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) throw new Error(`${provider.name} token refresh failed (${response.status})`);
|
||||
const tokens = (await response.json()) as OAuthTokens;
|
||||
if (!tokens.refresh_token) tokens.refresh_token = refreshToken;
|
||||
return tokens;
|
||||
}
|
||||
|
||||
export interface OidcDiscovery {
|
||||
issuer: string;
|
||||
authorization_endpoint: string;
|
||||
token_endpoint: string;
|
||||
userinfo_endpoint?: string;
|
||||
jwks_uri: string;
|
||||
revocation_endpoint?: string;
|
||||
}
|
||||
export async function discoverOidc(
|
||||
issuer: string,
|
||||
fetchImpl: typeof fetch = fetch,
|
||||
): Promise<OidcDiscovery> {
|
||||
const base = issuer.replace(/\/$/, "");
|
||||
const response = await fetchImpl(`${base}/.well-known/openid-configuration`);
|
||||
if (!response.ok) throw new Error(`OIDC discovery failed (${response.status})`);
|
||||
const value = (await response.json()) as OidcDiscovery;
|
||||
if (value.issuer !== issuer && value.issuer !== base) throw new Error("OIDC issuer mismatch");
|
||||
return value;
|
||||
}
|
||||
|
||||
export function validateOAuthReturnTo(
|
||||
value: string | undefined,
|
||||
origin: string,
|
||||
fallback = "/",
|
||||
): string {
|
||||
if (!value) return fallback;
|
||||
try {
|
||||
const url = new URL(value, origin);
|
||||
return url.origin === new URL(origin).origin
|
||||
? `${url.pathname}${url.search}${url.hash}`
|
||||
: fallback;
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
@@ -241,3 +241,11 @@ export async function fetchProfile(
|
||||
if (!res.ok) throw new Error(`${provider.name} userinfo failed (${res.status})`);
|
||||
return provider.mapProfile((await res.json()) as Record<string, unknown>);
|
||||
}
|
||||
export {
|
||||
memoryOAuthStateStore,
|
||||
createOAuthState,
|
||||
refreshOAuthTokens,
|
||||
discoverOidc,
|
||||
validateOAuthReturnTo,
|
||||
} from "./advanced.ts";
|
||||
export type { OAuthStateRecord, OAuthStateStore, OidcDiscovery } from "./advanced.ts";
|
||||
|
||||
Reference in New Issue
Block a user