very wip draft auth spec support

This commit is contained in:
Paul Carleton 2025-05-16 20:53:21 +01:00
commit befadc0042
No known key found for this signature in database
5 changed files with 53 additions and 11 deletions

View file

@ -125,8 +125,9 @@ const App = () => {
isInitiatingAuth: false, isInitiatingAuth: false,
oauthTokens: null, oauthTokens: null,
loading: true, loading: true,
oauthStep: "metadata_discovery", oauthStep: "resource_metadata_discovery",
oauthMetadata: null, oauthMetadata: null,
resourceMetadata: null,
oauthClientInfo: null, oauthClientInfo: null,
authorizationUrl: null, authorizationUrl: null,
authorizationCode: "", authorizationCode: "",

View file

@ -179,7 +179,7 @@ const AuthDebugger = ({
serverAuthProvider.clear(); serverAuthProvider.clear();
updateAuthState({ updateAuthState({
oauthTokens: null, oauthTokens: null,
oauthStep: "metadata_discovery", oauthStep: "resource_metadata_discovery",
latestError: null, latestError: null,
oauthClientInfo: null, oauthClientInfo: null,
authorizationCode: "", authorizationCode: "",

View file

@ -79,6 +79,16 @@ export const OAuthFlowProgress = ({
null, null,
); );
const steps: Array<OAuthStep> = [
"resource_metadata_discovery",
"metadata_discovery",
"client_registration",
"authorization_redirect",
"authorization_code",
"token_request",
"complete",
];
const currentStepIdx = steps.findIndex((s) => s === authState.oauthStep); const currentStepIdx = steps.findIndex((s) => s === authState.oauthStep);
useEffect(() => { useEffect(() => {
@ -124,6 +134,21 @@ export const OAuthFlowProgress = ({
</p> </p>
<div className="space-y-3"> <div className="space-y-3">
<OAuthStepDetails
label="Resource Metadata Discovery"
{...getStepProps("resource_metadata_discovery")}
>
{authState.resourceMetadata && (
<details className="text-xs mt-2">
<summary className="cursor-pointer text-muted-foreground font-medium">
Retrieved OAuth Resource Metadata from {(new URL('/.well-known/oauth-protected-resource', serverUrl)).href}
</summary>
<pre className="mt-2 p-2 bg-muted rounded-md overflow-auto max-h-[300px]">
{JSON.stringify(authState.resourceMetadata, null, 2)}
</pre>
</details>
)}
</OAuthStepDetails>
<OAuthStepDetails <OAuthStepDetails
label="Metadata Discovery" label="Metadata Discovery"
{...getStepProps("metadata_discovery")} {...getStepProps("metadata_discovery")}

View file

@ -7,6 +7,7 @@ import {
// OAuth flow steps // OAuth flow steps
export type OAuthStep = export type OAuthStep =
| "resource_metadata_discovery"
| "metadata_discovery" | "metadata_discovery"
| "client_registration" | "client_registration"
| "authorization_redirect" | "authorization_redirect"
@ -28,6 +29,8 @@ export interface AuthDebuggerState {
oauthTokens: OAuthTokens | null; oauthTokens: OAuthTokens | null;
loading: boolean; loading: boolean;
oauthStep: OAuthStep; oauthStep: OAuthStep;
// TODO: use sdk type
resourceMetadata: object | null;
oauthMetadata: OAuthMetadata | null; oauthMetadata: OAuthMetadata | null;
oauthClientInfo: OAuthClientInformationFull | OAuthClientInformation | null; oauthClientInfo: OAuthClientInformationFull | OAuthClientInformation | null;
authorizationUrl: string | null; authorizationUrl: string | null;

View file

@ -18,15 +18,34 @@ export interface StateMachineContext {
export interface StateTransition { export interface StateTransition {
canTransition: (context: StateMachineContext) => Promise<boolean>; canTransition: (context: StateMachineContext) => Promise<boolean>;
execute: (context: StateMachineContext) => Promise<void>; execute: (context: StateMachineContext) => Promise<void>;
nextStep: OAuthStep;
} }
// State machine transitions // State machine transitions
export const oauthTransitions: Record<OAuthStep, StateTransition> = { export const oauthTransitions: Record<OAuthStep, StateTransition> = {
metadata_discovery: { resource_metadata_discovery: {
canTransition: async () => true, canTransition: async () => true,
execute: async (context) => { execute: async (context) => {
const metadata = await discoverOAuthMetadata(context.serverUrl); // TODO: use sdk
const url = new URL("/.well-known/oauth-protected-resource", context.serverUrl);
const response = await fetch(url);
const resourceMetadata = await response.json();
context.updateState({
resourceMetadata: resourceMetadata,
oauthStep: "metadata_discovery",
});
},
},
metadata_discovery: {
canTransition: async (context) => !!context.state.resourceMetadata,
execute: async (context) => {
// TODO: use sdk
let authServerUrl = context.serverUrl;
if (context.state.resourceMetadata?.authorization_servers?.[0]) {
authServerUrl = context.state.resourceMetadata.authorization_servers[0];
}
const metadata = await discoverOAuthMetadata(authServerUrl);
if (!metadata) { if (!metadata) {
throw new Error("Failed to discover OAuth metadata"); throw new Error("Failed to discover OAuth metadata");
} }
@ -37,7 +56,6 @@ export const oauthTransitions: Record<OAuthStep, StateTransition> = {
oauthStep: "client_registration", oauthStep: "client_registration",
}); });
}, },
nextStep: "client_registration",
}, },
client_registration: { client_registration: {
@ -62,7 +80,6 @@ export const oauthTransitions: Record<OAuthStep, StateTransition> = {
oauthStep: "authorization_redirect", oauthStep: "authorization_redirect",
}); });
}, },
nextStep: "authorization_redirect",
}, },
authorization_redirect: { authorization_redirect: {
@ -93,7 +110,6 @@ export const oauthTransitions: Record<OAuthStep, StateTransition> = {
oauthStep: "authorization_code", oauthStep: "authorization_code",
}); });
}, },
nextStep: "authorization_code",
}, },
authorization_code: { authorization_code: {
@ -114,7 +130,6 @@ export const oauthTransitions: Record<OAuthStep, StateTransition> = {
oauthStep: "token_request", oauthStep: "token_request",
}); });
}, },
nextStep: "token_request",
}, },
token_request: { token_request: {
@ -144,7 +159,6 @@ export const oauthTransitions: Record<OAuthStep, StateTransition> = {
oauthStep: "complete", oauthStep: "complete",
}); });
}, },
nextStep: "complete",
}, },
complete: { complete: {
@ -152,7 +166,6 @@ export const oauthTransitions: Record<OAuthStep, StateTransition> = {
execute: async () => { execute: async () => {
// No-op for complete state // No-op for complete state
}, },
nextStep: "complete",
}, },
}; };