Skip to content

Commit 9dc4a4f

Browse files
committed
WIP /authorize endpoint
1 parent 6d33a7c commit 9dc4a4f

File tree

2 files changed

+90
-1
lines changed

2 files changed

+90
-1
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { RequestHandler } from "express";
2+
import { z } from "zod";
3+
import { OAuthRegisteredClientsStore } from "../clients.js";
4+
5+
export type AuthorizationHandlerOptions = {
6+
/**
7+
* A store used to read information about registered OAuth clients.
8+
*/
9+
store: OAuthRegisteredClientsStore;
10+
};
11+
12+
const ClientAuthorizationParamsSchema = z.object({
13+
client_id: z.string(),
14+
redirect_uri: z.string().optional(),
15+
});
16+
17+
const RequestAuthorizationParamsSchema = z.object({
18+
response_type: z.literal("code"),
19+
code_challenge: z.string(),
20+
code_challenge_method: z.literal("S256"),
21+
scope: z.string().optional(),
22+
state: z.string().optional(),
23+
});
24+
25+
export function authorizationHandler({ store }: AuthorizationHandlerOptions): RequestHandler {
26+
return async (req, res) => {
27+
if (req.method !== "GET" && req.method !== "POST") {
28+
res.status(405).end("Method Not Allowed");
29+
return;
30+
}
31+
32+
let client_id, redirect_uri;
33+
try {
34+
({ client_id, redirect_uri } = ClientAuthorizationParamsSchema.parse(req.query));
35+
} catch (error) {
36+
res.status(400).end(`Bad Request: ${error}`);
37+
return;
38+
}
39+
40+
const client = await store.getClient(client_id);
41+
if (!client) {
42+
res.status(400).end("Bad Request: invalid client_id");
43+
return;
44+
}
45+
46+
if (redirect_uri !== undefined) {
47+
if (!client.redirect_uris.includes(redirect_uri)) {
48+
res.status(400).end("Bad Request: invalid redirect_uri");
49+
return;
50+
}
51+
} else if (client.redirect_uris.length === 1) {
52+
redirect_uri = client.redirect_uris[0];
53+
} else {
54+
res.status(400).end("Bad Request: missing redirect_uri");
55+
return;
56+
}
57+
58+
let params;
59+
try {
60+
params = RequestAuthorizationParamsSchema.parse(req.query);
61+
} catch (error) {
62+
const errorUrl = new URL(redirect_uri);
63+
errorUrl.searchParams.set("error", "invalid_request");
64+
errorUrl.searchParams.set("error_description", String(error));
65+
res.redirect(302, errorUrl.href);
66+
return;
67+
}
68+
69+
if (params.scope !== undefined && client.scope !== undefined) {
70+
const requestedScopes = params.scope.split(" ");
71+
const allowedScopes = new Set(client.scope.split(" "));
72+
73+
// If any requested scope is not in the client's registered scopes, error out
74+
for (const scope of requestedScopes) {
75+
if (!allowedScopes.has(scope)) {
76+
const errorUrl = new URL(redirect_uri);
77+
errorUrl.searchParams.set("error", "invalid_scope");
78+
errorUrl.searchParams.set("error_description", `Client was not registered with scope ${scope}`);
79+
res.redirect(302, errorUrl.href);
80+
return;
81+
}
82+
}
83+
}
84+
85+
// TODO: Store code challenge
86+
// TODO: Generate authorization code
87+
// TODO: Redirect to redirect_uri (handle in calling code)
88+
};
89+
}

src/server/auth/handlers/register.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export function clientRegistrationHandler({ store, clientSecretExpirySeconds = D
2525
throw new Error("Client registration store does not support registering clients");
2626
}
2727

28-
// Nested router so we can configure middleware
28+
// Nested router so we can configure middleware and restrict HTTP method
2929
const router = express.Router();
3030
router.use(express.json());
3131

0 commit comments

Comments
 (0)