forked from mirrors/misskey
fix: missing spec implementation
This commit is contained in:
parent
b25c031179
commit
ca5dc65b67
3 changed files with 49 additions and 0 deletions
|
|
@ -34,6 +34,7 @@ import { MemoryKVCache } from '@/misc/cache.js';
|
|||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import Logger from '@/logger.js';
|
||||
import { StatusError } from '@/misc/status-error.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { HtmlTemplateService } from '@/server/web/HtmlTemplateService.js';
|
||||
import { OAuthPage } from '@/server/web/views/oauth.js';
|
||||
import type { FastifyInstance, FastifyReply } from 'fastify';
|
||||
|
|
@ -392,6 +393,7 @@ export class OAuth2ProviderService {
|
|||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
private cacheService: CacheService,
|
||||
private userEntityService: UserEntityService,
|
||||
loggerService: LoggerService,
|
||||
private htmlTemplateService: HtmlTemplateService,
|
||||
) {
|
||||
|
|
@ -556,6 +558,12 @@ export class OAuth2ProviderService {
|
|||
}
|
||||
});
|
||||
|
||||
// https://indieauth.spec.indieweb.org/#profile-url-response
|
||||
// 11 July 2024 spec also allows redeeming an authorization code at the
|
||||
// authorization endpoint itself when the client only needs the canonical
|
||||
// profile URL and not an access token. Misskey currently uses this route
|
||||
// only for the browser-based consent UI and issues tokens through
|
||||
// /oauth/token, so the profile-only redemption flow remains unimplemented.
|
||||
fastify.post('/decision', async (request, reply) => {
|
||||
try {
|
||||
const body = toRequestParameters(request.body);
|
||||
|
|
@ -675,10 +683,21 @@ export class OAuth2ProviderService {
|
|||
}
|
||||
checkPKCE(codeVerifier, granted.codeChallenge, 'S256');
|
||||
|
||||
// https://indieauth.spec.indieweb.org/#access-token-response
|
||||
// The token response MUST include the canonical profile URL as `me`.
|
||||
// Misskey uses the stable local actor URL so clients can later confirm
|
||||
// that the returned profile URL declares the same authorization server.
|
||||
const me = this.userEntityService.genLocalUserUri(granted.userId);
|
||||
|
||||
const accessToken = secureRndstr(128);
|
||||
const now = new Date();
|
||||
|
||||
// NOTE: we don't have a setup for automatic token expiration
|
||||
// https://indieauth.spec.indieweb.org/#access-token-response
|
||||
// `expires_in` is only RECOMMENDED there, and RFC6749 Section 5.1 also
|
||||
// allows omitting it when the server documents or otherwise defines the
|
||||
// token lifetime. Misskey currently issues bearer tokens without a
|
||||
// published expiration timestamp.
|
||||
await this.accessTokensRepository.insert({
|
||||
id: this.idService.gen(now.getTime()),
|
||||
lastUsedAt: now,
|
||||
|
|
@ -702,6 +721,7 @@ export class OAuth2ProviderService {
|
|||
access_token: accessToken,
|
||||
token_type: 'Bearer',
|
||||
scope: granted.scopes.join(' '),
|
||||
me,
|
||||
});
|
||||
} catch (error) {
|
||||
sendOAuthError(reply, normalizeOAuthError(error));
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@ export function UserPage(props: CommonProps<{
|
|||
|
||||
{props.sub == null && props.federationEnabled ? (
|
||||
<>
|
||||
{props.user.host == null ? <link rel="indieauth-metadata" href={`${props.config.url}/.well-known/oauth-authorization-server`} /> : null}
|
||||
{props.user.host == null ? <link rel="authorization_endpoint" href={`${props.config.url}/oauth/authorize`} /> : null}
|
||||
{props.user.host == null ? <link rel="token_endpoint" href={`${props.config.url}/oauth/token`} /> : null}
|
||||
{props.user.host == null ? <link rel="alternate" type="application/activity+json" href={`${props.config.url}/users/${props.user.id}`} /> : null}
|
||||
{props.user.uri != null ? <link rel="alternate" type="application/activity+json" href={props.user.uri} /> : null}
|
||||
{props.profile.url != null ? <link rel="alternate" type="text/html" href={props.profile.url} /> : null}
|
||||
|
|
|
|||
|
|
@ -83,6 +83,11 @@ function getMeta(html: string): { transactionId: string | undefined, clientName:
|
|||
};
|
||||
}
|
||||
|
||||
function getLinkHref(html: string, rel: string): string | undefined {
|
||||
const doc = htmlParser.parse(`<div>${html}</div>`);
|
||||
return doc.querySelector(`link[rel="${rel}"]`)?.attributes.href;
|
||||
}
|
||||
|
||||
function fetchDecision(transactionId: string, user: misskey.entities.SignupResponse, { cancel }: { cancel?: boolean } = {}): Promise<Response> {
|
||||
return fetch(new URL('/oauth/decision', host), {
|
||||
method: 'post',
|
||||
|
|
@ -232,6 +237,27 @@ describe('OAuth', () => {
|
|||
assert.strictEqual(typeof token.token.access_token, 'string');
|
||||
assert.strictEqual(token.token.token_type, 'Bearer');
|
||||
assert.strictEqual(token.token.scope, 'write:notes');
|
||||
// https://indieauth.spec.indieweb.org/#access-token-response
|
||||
assert.strictEqual(token.token.me, `http://misskey.local/users/${alice.id}`);
|
||||
|
||||
// https://indieauth.spec.indieweb.org/#authorization-server-confirmation
|
||||
// Clients must be able to rediscover the same authorization server
|
||||
// from the returned canonical profile URL.
|
||||
const meResponse = await fetch(token.token.me as string);
|
||||
assert.strictEqual(meResponse.status, 200);
|
||||
const metadataHref = getLinkHref(await meResponse.text(), 'indieauth-metadata');
|
||||
assert.strictEqual(metadataHref, 'http://misskey.local/.well-known/oauth-authorization-server');
|
||||
|
||||
const metadataResponse = await fetch(metadataHref as string);
|
||||
assert.strictEqual(metadataResponse.status, 200);
|
||||
const metadata = await metadataResponse.json() as {
|
||||
issuer: string;
|
||||
authorization_endpoint: string;
|
||||
token_endpoint: string;
|
||||
};
|
||||
assert.strictEqual(metadata.issuer, 'http://misskey.local');
|
||||
assert.strictEqual(metadata.authorization_endpoint, 'http://misskey.local/oauth/authorize');
|
||||
assert.strictEqual(metadata.token_endpoint, 'http://misskey.local/oauth/token');
|
||||
|
||||
const createResult = await api('notes/create', { text: 'test' }, {
|
||||
token: token.token.access_token as string,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue