grant type tests
This commit is contained in:
parent
b57d40ed09
commit
628377187a
2 changed files with 90 additions and 14 deletions
|
@ -18,11 +18,11 @@ import type { Config } from '@/config.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import type { AccessTokensRepository, UsersRepository } from '@/models/index.js';
|
import type { AccessTokensRepository, UsersRepository } from '@/models/index.js';
|
||||||
import type { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import type { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import type { LocalUser } from '@/models/entities/User.js';
|
import type { LocalUser } from '@/models/entities/User.js';
|
||||||
import { MemoryKVCache } from '@/misc/cache.js';
|
import { MemoryKVCache } from '@/misc/cache.js';
|
||||||
import type { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import Logger from '@/logger.js';
|
import Logger from '@/logger.js';
|
||||||
import type { ServerResponse } from 'node:http';
|
import type { ServerResponse } from 'node:http';
|
||||||
import type { FastifyInstance } from 'fastify';
|
import type { FastifyInstance } from 'fastify';
|
||||||
|
@ -376,9 +376,9 @@ export class OAuth2ProviderService {
|
||||||
}
|
}
|
||||||
areq.scope = scopes;
|
areq.scope = scopes;
|
||||||
|
|
||||||
if (type !== 'code') {
|
// Require PKCE parameters.
|
||||||
throw new AuthorizationError('`response_type` parameter must be set as "code"', 'invalid_request');
|
// Recommended by https://indieauth.spec.indieweb.org/#authorization-request, but also prevents downgrade attack:
|
||||||
}
|
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#name-pkce-downgrade-attack
|
||||||
if (typeof codeChallenge !== 'string') {
|
if (typeof codeChallenge !== 'string') {
|
||||||
throw new AuthorizationError('`code_challenge` parameter is required', 'invalid_request');
|
throw new AuthorizationError('`code_challenge` parameter is required', 'invalid_request');
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { AuthorizationCode, type AuthorizationTokenConfig } from 'simple-oauth2';
|
import { AuthorizationCode, ResourceOwnerPassword, type AuthorizationTokenConfig, ClientCredentials } from 'simple-oauth2';
|
||||||
import pkceChallenge from 'pkce-challenge';
|
import pkceChallenge from 'pkce-challenge';
|
||||||
import { JSDOM } from 'jsdom';
|
import { JSDOM } from 'jsdom';
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
@ -375,7 +375,10 @@ describe('OAuth', () => {
|
||||||
code,
|
code,
|
||||||
redirect_uri,
|
redirect_uri,
|
||||||
code_verifier: wrong_verifier,
|
code_verifier: wrong_verifier,
|
||||||
} as AuthorizationTokenConfigExtended));
|
} as AuthorizationTokenConfigExtended), (err: any) => {
|
||||||
|
assert.strictEqual(err.data.payload.error, 'invalid_grant');
|
||||||
|
return true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -400,20 +403,29 @@ describe('OAuth', () => {
|
||||||
code,
|
code,
|
||||||
redirect_uri,
|
redirect_uri,
|
||||||
code_verifier,
|
code_verifier,
|
||||||
} as AuthorizationTokenConfigExtended));
|
} as AuthorizationTokenConfigExtended), (err: any) => {
|
||||||
|
assert.strictEqual(err.data.payload.error, 'invalid_grant');
|
||||||
|
return true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('On failure', async () => {
|
test('On failure', async () => {
|
||||||
const { code_challenge, code_verifier } = await pkceChallenge(128);
|
const { code_challenge, code_verifier } = await pkceChallenge(128);
|
||||||
const { client, code } = await fetchAuthorizationCode(alice, 'write:notes', code_challenge);
|
const { client, code } = await fetchAuthorizationCode(alice, 'write:notes', code_challenge);
|
||||||
|
|
||||||
await assert.rejects(client.getToken({ code, redirect_uri }));
|
await assert.rejects(client.getToken({ code, redirect_uri }), (err: any) => {
|
||||||
|
assert.strictEqual(err.data.payload.error, 'invalid_grant');
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
await assert.rejects(client.getToken({
|
await assert.rejects(client.getToken({
|
||||||
code,
|
code,
|
||||||
redirect_uri,
|
redirect_uri,
|
||||||
code_verifier,
|
code_verifier,
|
||||||
} as AuthorizationTokenConfigExtended));
|
} as AuthorizationTokenConfigExtended), (err: any) => {
|
||||||
|
assert.strictEqual(err.data.payload.error, 'invalid_grant');
|
||||||
|
return true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -660,7 +672,10 @@ describe('OAuth', () => {
|
||||||
code,
|
code,
|
||||||
redirect_uri: 'http://127.0.0.2/',
|
redirect_uri: 'http://127.0.0.2/',
|
||||||
code_verifier,
|
code_verifier,
|
||||||
} as AuthorizationTokenConfigExtended));
|
} as AuthorizationTokenConfigExtended), (err: any) => {
|
||||||
|
assert.strictEqual(err.data.payload.error, 'invalid_grant');
|
||||||
|
return true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Invalid redirect_uri including the valid one at token endpoint', async () => {
|
test('Invalid redirect_uri including the valid one at token endpoint', async () => {
|
||||||
|
@ -672,7 +687,10 @@ describe('OAuth', () => {
|
||||||
code,
|
code,
|
||||||
redirect_uri: 'http://127.0.0.1/redirection',
|
redirect_uri: 'http://127.0.0.1/redirection',
|
||||||
code_verifier,
|
code_verifier,
|
||||||
} as AuthorizationTokenConfigExtended));
|
} as AuthorizationTokenConfigExtended), (err: any) => {
|
||||||
|
assert.strictEqual(err.data.payload.error, 'invalid_grant');
|
||||||
|
return true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('No redirect_uri at token endpoint', async () => {
|
test('No redirect_uri at token endpoint', async () => {
|
||||||
|
@ -683,7 +701,10 @@ describe('OAuth', () => {
|
||||||
await assert.rejects(client.getToken({
|
await assert.rejects(client.getToken({
|
||||||
code,
|
code,
|
||||||
code_verifier,
|
code_verifier,
|
||||||
} as AuthorizationTokenConfigExtended));
|
} as AuthorizationTokenConfigExtended), (err: any) => {
|
||||||
|
assert.strictEqual(err.data.payload.error, 'invalid_grant');
|
||||||
|
return true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -752,6 +773,61 @@ describe('OAuth', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Only authorization code grant is supported
|
||||||
|
describe('Grant type', () => {
|
||||||
|
test('Implicit grant is not supported', async () => {
|
||||||
|
const url = new URL('/oauth/authorize', host);
|
||||||
|
url.searchParams.append('response_type', 'token');
|
||||||
|
const response = await fetch(url);
|
||||||
|
assertDirectError(response, 501, 'unsupported_response_type');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Resource owner grant is not supported', async () => {
|
||||||
|
const client = new ResourceOwnerPassword({
|
||||||
|
client: {
|
||||||
|
id: `http://127.0.0.1:${clientPort}/`,
|
||||||
|
secret: '',
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
tokenHost: host,
|
||||||
|
tokenPath: '/oauth/token',
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
authorizationMethod: 'body',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await assert.rejects(client.getToken({
|
||||||
|
username: 'alice',
|
||||||
|
password: 'test',
|
||||||
|
}), (err: any) => {
|
||||||
|
assert.strictEqual(err.data.payload.error, 'unsupported_grant_type');
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Client credential grant is not supported', async () => {
|
||||||
|
const client = new ClientCredentials({
|
||||||
|
client: {
|
||||||
|
id: `http://127.0.0.1:${clientPort}/`,
|
||||||
|
secret: '',
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
tokenHost: host,
|
||||||
|
tokenPath: '/oauth/token',
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
authorizationMethod: 'body',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await assert.rejects(client.getToken({}), (err: any) => {
|
||||||
|
assert.strictEqual(err.data.payload.error, 'unsupported_grant_type');
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// https://indieauth.spec.indieweb.org/#client-information-discovery
|
// https://indieauth.spec.indieweb.org/#client-information-discovery
|
||||||
describe('Client Information Discovery', () => {
|
describe('Client Information Discovery', () => {
|
||||||
describe('Redirection', () => {
|
describe('Redirection', () => {
|
||||||
|
|
Loading…
Reference in a new issue