mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-06-25 17:10:43 +00:00
fix(backend/oauth2): Token Grantエンドポイントのバリデーションを修正 (#17580)
This commit is contained in:
parent
bbcce5b49d
commit
d7c11a61c5
2 changed files with 92 additions and 16 deletions
|
|
@ -273,8 +273,9 @@ async function discoverClientInformation(logger: Logger, httpRequestService: Htt
|
|||
}
|
||||
}
|
||||
|
||||
function firstValue(value: string | string[] | undefined): string | undefined {
|
||||
return Array.isArray(value) ? value[0] : value;
|
||||
function firstValue(value: unknown | unknown[] | undefined): string | undefined {
|
||||
const firstElement = Array.isArray(value) ? value[0] : value;
|
||||
return typeof firstElement === 'string' ? firstElement : undefined;
|
||||
}
|
||||
|
||||
function normalizeScope(scope: string | string[] | undefined): string[] {
|
||||
|
|
@ -282,12 +283,39 @@ function normalizeScope(scope: string | string[] | undefined): string[] {
|
|||
return raw.flatMap(value => value.split(/\s+/)).filter(Boolean);
|
||||
}
|
||||
|
||||
function parseUrlEncodedParameters(rawBody: string): OAuthRequestParameters {
|
||||
const parsed: OAuthRequestParameters = {};
|
||||
for (const [key, value] of new URLSearchParams(rawBody).entries()) {
|
||||
const current = parsed[key];
|
||||
if (current == null) {
|
||||
parsed[key] = value;
|
||||
} else if (Array.isArray(current)) {
|
||||
current.push(value);
|
||||
} else {
|
||||
parsed[key] = [current, value];
|
||||
}
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function toRequestParameters(body: unknown): OAuthRequestParameters {
|
||||
if (typeof body === 'string') {
|
||||
return parseUrlEncodedParameters(body);
|
||||
}
|
||||
|
||||
if (body instanceof URLSearchParams) {
|
||||
return parseUrlEncodedParameters(body.toString());
|
||||
}
|
||||
|
||||
if (body == null || typeof body !== 'object' || Array.isArray(body)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return body as OAuthRequestParameters;
|
||||
return Object.fromEntries(Object.entries(body).filter(([_, value]) => (
|
||||
typeof value === 'string' ||
|
||||
(Array.isArray(value) && value.every(v => typeof v === 'string'))
|
||||
)));
|
||||
}
|
||||
|
||||
function applyNoStore(reply: FastifyReply): void {
|
||||
|
|
@ -360,19 +388,7 @@ function registerFormBodyParser(fastify: FastifyInstance): void {
|
|||
|
||||
fastify.addContentTypeParser('application/x-www-form-urlencoded', { parseAs: 'string' }, (_request, body, done) => {
|
||||
try {
|
||||
const parsed: OAuthRequestParameters = {};
|
||||
for (const [key, value] of new URLSearchParams(typeof body === 'string' ? body : body.toString('utf8')).entries()) {
|
||||
const current = parsed[key];
|
||||
if (current == null) {
|
||||
parsed[key] = value;
|
||||
} else if (Array.isArray(current)) {
|
||||
current.push(value);
|
||||
} else {
|
||||
parsed[key] = [current, value];
|
||||
}
|
||||
}
|
||||
|
||||
done(null, parsed);
|
||||
done(null, parseUrlEncodedParameters(typeof body === 'string' ? body : body.toString('utf8')));
|
||||
} catch (error) {
|
||||
done(error as Error, undefined);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -809,6 +809,66 @@ describe('OAuth', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Token endpoint', () => {
|
||||
test('Accept JSON payload', async () => {
|
||||
const { code_challenge, code_verifier } = await pkceChallenge(128);
|
||||
const { code } = await fetchAuthorizationCode(alice, 'write:notes', code_challenge);
|
||||
|
||||
const response = await fetch(new URL('/oauth/token', host), {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
grant_type: 'authorization_code',
|
||||
code,
|
||||
client_id: clientConfig.client.id,
|
||||
redirect_uri,
|
||||
code_verifier,
|
||||
}),
|
||||
});
|
||||
|
||||
assert.strictEqual(response.status, 200);
|
||||
const tokenResponse = await response.json() as {
|
||||
access_token: string;
|
||||
token_type: string;
|
||||
scope: string;
|
||||
};
|
||||
assert.strictEqual(typeof tokenResponse.access_token, 'string');
|
||||
assert.strictEqual(tokenResponse.token_type, 'Bearer');
|
||||
assert.strictEqual(tokenResponse.scope, 'write:notes');
|
||||
});
|
||||
|
||||
test('Accept x-www-form-urlencoded payload', async () => {
|
||||
const { code_challenge, code_verifier } = await pkceChallenge(128);
|
||||
const { code } = await fetchAuthorizationCode(alice, 'write:notes', code_challenge);
|
||||
|
||||
const response = await fetch(new URL('/oauth/token', host), {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
grant_type: 'authorization_code',
|
||||
code,
|
||||
client_id: clientConfig.client.id,
|
||||
redirect_uri,
|
||||
code_verifier,
|
||||
}),
|
||||
});
|
||||
|
||||
assert.strictEqual(response.status, 200);
|
||||
const tokenResponse = await response.json() as {
|
||||
access_token: string;
|
||||
token_type: string;
|
||||
scope: string;
|
||||
};
|
||||
assert.strictEqual(typeof tokenResponse.access_token, 'string');
|
||||
assert.strictEqual(tokenResponse.token_type, 'Bearer');
|
||||
assert.strictEqual(tokenResponse.scope, 'write:notes');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Client Information Discovery', () => {
|
||||
// https://indieauth.spec.indieweb.org/#client-information-discovery
|
||||
describe('JSON client metadata (11 July 2024)', () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue