concurrent flow test
This commit is contained in:
parent
a688bd1061
commit
027c5734a4
|
@ -76,33 +76,6 @@ function validateClientId(raw: string): URL {
|
|||
return url;
|
||||
}
|
||||
|
||||
// const grantable = new Set([
|
||||
// 'AccessToken',
|
||||
// 'AuthorizationCode',
|
||||
// 'RefreshToken',
|
||||
// 'DeviceCode',
|
||||
// 'BackchannelAuthenticationRequest',
|
||||
// ]);
|
||||
|
||||
// const consumable = new Set([
|
||||
// 'AuthorizationCode',
|
||||
// 'RefreshToken',
|
||||
// 'DeviceCode',
|
||||
// 'BackchannelAuthenticationRequest',
|
||||
// ]);
|
||||
|
||||
// function grantKeyFor(id: string): string {
|
||||
// return `grant:${id}`;
|
||||
// }
|
||||
|
||||
// function userCodeKeyFor(userCode: string): string {
|
||||
// return `userCode:${userCode}`;
|
||||
// }
|
||||
|
||||
// function uidKeyFor(uid: string): string {
|
||||
// return `uid:${uid}`;
|
||||
// }
|
||||
|
||||
interface ClientInformation {
|
||||
id: string;
|
||||
redirectUris: string[];
|
||||
|
|
|
@ -4,9 +4,10 @@ import * as assert from 'assert';
|
|||
import { AuthorizationCode } from 'simple-oauth2';
|
||||
import pkceChallenge from 'pkce-challenge';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import * as misskey from 'misskey-js';
|
||||
import Fastify, { type FastifyInstance } from 'fastify';
|
||||
import { port, relativeFetch, signup, startServer } from '../utils.js';
|
||||
import type { INestApplicationContext } from '@nestjs/common';
|
||||
import Fastify, { type FastifyInstance } from 'fastify';
|
||||
|
||||
const host = `http://127.0.0.1:${port}`;
|
||||
|
||||
|
@ -37,7 +38,7 @@ function getMeta(html: string): { transactionId: string | undefined, clientName:
|
|||
};
|
||||
}
|
||||
|
||||
function fetchDecision(cookie: string, transactionId: string, user: any, { cancel }: { cancel?: boolean } = {}): Promise<Response> {
|
||||
function fetchDecision(cookie: string, transactionId: string, user: misskey.entities.MeSignup, { cancel }: { cancel?: boolean } = {}): Promise<Response> {
|
||||
return fetch(new URL('/oauth/decision', host), {
|
||||
method: 'post',
|
||||
body: new URLSearchParams({
|
||||
|
@ -53,7 +54,7 @@ function fetchDecision(cookie: string, transactionId: string, user: any, { cance
|
|||
});
|
||||
}
|
||||
|
||||
async function fetchDecisionFromResponse(response: Response, user: any, { cancel }: { cancel?: boolean } = {}): Promise<Response> {
|
||||
async function fetchDecisionFromResponse(response: Response, user: misskey.entities.MeSignup, { cancel }: { cancel?: boolean } = {}): Promise<Response> {
|
||||
const cookie = response.headers.get('set-cookie');
|
||||
const { transactionId } = getMeta(await response.text());
|
||||
|
||||
|
@ -64,11 +65,13 @@ describe('OAuth', () => {
|
|||
let app: INestApplicationContext;
|
||||
let fastify: FastifyInstance;
|
||||
|
||||
let alice: any;
|
||||
let alice: misskey.entities.MeSignup;
|
||||
let bob: misskey.entities.MeSignup;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await startServer();
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
}, 1000 * 60 * 2);
|
||||
|
||||
beforeEach(async () => {
|
||||
|
@ -145,6 +148,81 @@ describe('OAuth', () => {
|
|||
assert.strictEqual(createResponseBody.createdNote.text, 'test');
|
||||
});
|
||||
|
||||
test('Two concurrent flows', async () => {
|
||||
const client = getClient();
|
||||
|
||||
const pkceAlice = pkceChallenge.default(128);
|
||||
const pkceBob = pkceChallenge.default(128);
|
||||
|
||||
const responseAlice = await fetch(client.authorizeURL({
|
||||
redirect_uri,
|
||||
scope: 'write:notes',
|
||||
state: 'state',
|
||||
code_challenge: pkceAlice.code_challenge,
|
||||
code_challenge_method: 'S256',
|
||||
}));
|
||||
assert.strictEqual(responseAlice.status, 200);
|
||||
|
||||
const responseBob = await fetch(client.authorizeURL({
|
||||
redirect_uri,
|
||||
scope: 'write:notes',
|
||||
state: 'state',
|
||||
code_challenge: pkceBob.code_challenge,
|
||||
code_challenge_method: 'S256',
|
||||
}));
|
||||
assert.strictEqual(responseBob.status, 200);
|
||||
|
||||
const decisionResponseAlice = await fetchDecisionFromResponse(responseAlice, alice);
|
||||
assert.strictEqual(decisionResponseAlice.status, 302);
|
||||
|
||||
const decisionResponseBob = await fetchDecisionFromResponse(responseBob, bob);
|
||||
assert.strictEqual(decisionResponseBob.status, 302);
|
||||
|
||||
const locationAlice = new URL(decisionResponseAlice.headers.get('location')!);
|
||||
assert.ok(locationAlice.searchParams.has('code'));
|
||||
|
||||
const locationBob = new URL(decisionResponseBob.headers.get('location')!);
|
||||
assert.ok(locationBob.searchParams.has('code'));
|
||||
|
||||
const tokenAlice = await client.getToken({
|
||||
code: locationAlice.searchParams.get('code')!,
|
||||
redirect_uri,
|
||||
code_verifier: pkceAlice.code_verifier,
|
||||
});
|
||||
|
||||
const tokenBob = await client.getToken({
|
||||
code: locationBob.searchParams.get('code')!,
|
||||
redirect_uri,
|
||||
code_verifier: pkceBob.code_verifier,
|
||||
});
|
||||
|
||||
const createResponseAlice = await relativeFetch('api/notes/create', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokenAlice.token.access_token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ text: 'test' }),
|
||||
});
|
||||
assert.strictEqual(createResponseAlice.status, 200);
|
||||
|
||||
const createResponseBob = await relativeFetch('api/notes/create', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokenBob.token.access_token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ text: 'test' }),
|
||||
});
|
||||
assert.strictEqual(createResponseAlice.status, 200);
|
||||
|
||||
const createResponseBodyAlice = await createResponseAlice.json() as { createdNote: misskey.entities.Note };
|
||||
assert.strictEqual(createResponseBodyAlice.createdNote.user.username, 'alice');
|
||||
|
||||
const createResponseBodyBob = await createResponseBob.json() as { createdNote: misskey.entities.Note };
|
||||
assert.strictEqual(createResponseBodyBob.createdNote.user.username, 'bob');
|
||||
});
|
||||
|
||||
describe('PKCE', () => {
|
||||
test('Require PKCE', async () => {
|
||||
const client = getClient();
|
||||
|
@ -213,6 +291,8 @@ describe('OAuth', () => {
|
|||
code_verifier: code_verifier + 'x',
|
||||
}));
|
||||
|
||||
// TODO: The following patterns may fail only because of pattern 1's failure. Let's split them.
|
||||
|
||||
// Pattern 2: clipped code
|
||||
await assert.rejects(client.getToken({
|
||||
code,
|
||||
|
@ -776,7 +856,5 @@ describe('OAuth', () => {
|
|||
});
|
||||
});
|
||||
|
||||
// TODO: authorizing two users concurrently
|
||||
|
||||
// TODO: Error format required by OAuth spec
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@ import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
|||
import { entities } from '../src/postgres.js';
|
||||
import { loadConfig } from '../src/config.js';
|
||||
import type * as misskey from 'misskey-js';
|
||||
import type { MeSignup } from 'misskey-js/built/entities.js';
|
||||
|
||||
export { server as startServer } from '@/boot/common.js';
|
||||
|
||||
|
|
Loading…
Reference in New Issue