concurrent flow test
This commit is contained in:
		@@ -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';
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user