some edits for comments
This commit is contained in:
parent
c55d9784fe
commit
1755c75647
1 changed files with 25 additions and 13 deletions
|
@ -35,14 +35,14 @@ import type { FastifyInstance } from 'fastify';
|
||||||
// This is also mostly similar to https://developers.google.com/identity/protocols/oauth2/web-server#uri-validation
|
// This is also mostly similar to https://developers.google.com/identity/protocols/oauth2/web-server#uri-validation
|
||||||
// although Google has stricter rule.
|
// although Google has stricter rule.
|
||||||
function validateClientId(raw: string): URL {
|
function validateClientId(raw: string): URL {
|
||||||
// Clients are identified by a [URL].
|
// "Clients are identified by a [URL]."
|
||||||
const url = ((): URL => {
|
const url = ((): URL => {
|
||||||
try {
|
try {
|
||||||
return new URL(raw);
|
return new URL(raw);
|
||||||
} catch { throw new AuthorizationError('client_id must be a valid URL', 'invalid_request'); }
|
} catch { throw new AuthorizationError('client_id must be a valid URL', 'invalid_request'); }
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Client identifier URLs MUST have either an https or http scheme
|
// "Client identifier URLs MUST have either an https or http scheme"
|
||||||
// But then again:
|
// But then again:
|
||||||
// https://datatracker.ietf.org/doc/html/rfc6749.html#section-3.1.2.1
|
// https://datatracker.ietf.org/doc/html/rfc6749.html#section-3.1.2.1
|
||||||
// 'The redirection endpoint SHOULD require the use of TLS as described
|
// 'The redirection endpoint SHOULD require the use of TLS as described
|
||||||
|
@ -53,30 +53,30 @@ function validateClientId(raw: string): URL {
|
||||||
throw new AuthorizationError('client_id must be a valid HTTPS URL', 'invalid_request');
|
throw new AuthorizationError('client_id must be a valid HTTPS URL', 'invalid_request');
|
||||||
}
|
}
|
||||||
|
|
||||||
// MUST contain a path component (new URL() implicitly adds one)
|
// "MUST contain a path component (new URL() implicitly adds one)"
|
||||||
|
|
||||||
// MUST NOT contain single-dot or double-dot path segments,
|
// "MUST NOT contain single-dot or double-dot path segments,"
|
||||||
const segments = url.pathname.split('/');
|
const segments = url.pathname.split('/');
|
||||||
if (segments.includes('.') || segments.includes('..')) {
|
if (segments.includes('.') || segments.includes('..')) {
|
||||||
throw new AuthorizationError('client_id must not contain dot path segments', 'invalid_request');
|
throw new AuthorizationError('client_id must not contain dot path segments', 'invalid_request');
|
||||||
}
|
}
|
||||||
|
|
||||||
// (MAY contain a query string component)
|
// ("MAY contain a query string component")
|
||||||
|
|
||||||
// MUST NOT contain a fragment component
|
// "MUST NOT contain a fragment component"
|
||||||
if (url.hash) {
|
if (url.hash) {
|
||||||
throw new AuthorizationError('client_id must not contain a fragment component', 'invalid_request');
|
throw new AuthorizationError('client_id must not contain a fragment component', 'invalid_request');
|
||||||
}
|
}
|
||||||
|
|
||||||
// MUST NOT contain a username or password component
|
// "MUST NOT contain a username or password component"
|
||||||
if (url.username || url.password) {
|
if (url.username || url.password) {
|
||||||
throw new AuthorizationError('client_id must not contain a username or a password', 'invalid_request');
|
throw new AuthorizationError('client_id must not contain a username or a password', 'invalid_request');
|
||||||
}
|
}
|
||||||
|
|
||||||
// (MAY contain a port)
|
// ("MAY contain a port")
|
||||||
|
|
||||||
// host names MUST be domain names or a loopback interface and MUST NOT be
|
// "host names MUST be domain names or a loopback interface and MUST NOT be
|
||||||
// IPv4 or IPv6 addresses except for IPv4 127.0.0.1 or IPv6 [::1].
|
// IPv4 or IPv6 addresses except for IPv4 127.0.0.1 or IPv6 [::1]."
|
||||||
if (!url.hostname.match(/\.\w+$/) && !['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)) {
|
if (!url.hostname.match(/\.\w+$/) && !['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)) {
|
||||||
throw new AuthorizationError('client_id must have a domain name as a host name', 'invalid_request');
|
throw new AuthorizationError('client_id must have a domain name as a host name', 'invalid_request');
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,16 @@ interface ClientInformation {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://indieauth.spec.indieweb.org/#client-information-discovery
|
// https://indieauth.spec.indieweb.org/#client-information-discovery
|
||||||
|
// "Authorization servers SHOULD support parsing the [h-app] Microformat from the client_id,
|
||||||
|
// and if there is an [h-app] with a url property matching the client_id URL,
|
||||||
|
// then it should use the name and icon and display them on the authorization prompt."
|
||||||
|
// (But we don't display any icon for now)
|
||||||
|
// https://indieauth.spec.indieweb.org/#redirect-url
|
||||||
|
// "The client SHOULD publish one or more <link> tags or Link HTTP headers with a rel attribute
|
||||||
|
// of redirect_uri at the client_id URL.
|
||||||
|
// Authorization endpoints verifying that a redirect_uri is allowed for use by a client MUST
|
||||||
|
// look for an exact match of the given redirect_uri in the request against the list of
|
||||||
|
// redirect_uris discovered after resolving any relative URLs."
|
||||||
async function discoverClientInformation(httpRequestService: HttpRequestService, id: string): Promise<ClientInformation> {
|
async function discoverClientInformation(httpRequestService: HttpRequestService, id: string): Promise<ClientInformation> {
|
||||||
try {
|
try {
|
||||||
const res = await httpRequestService.send(id);
|
const res = await httpRequestService.send(id);
|
||||||
|
@ -341,9 +351,8 @@ export class OAuth2ProviderService {
|
||||||
const clientUrl = validateClientId(clientID);
|
const clientUrl = validateClientId(clientID);
|
||||||
|
|
||||||
// TODO: Consider allowing localhost for native apps (RFC 8252)
|
// TODO: Consider allowing localhost for native apps (RFC 8252)
|
||||||
// The current setup requires an explicit list of redirect_uris per
|
// This is currently blocked by the redirect_uri check below, but we can theoretically
|
||||||
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.1.3
|
// loosen the rule for localhost as the data never leaves the client machine.
|
||||||
// which blocks the support. But we could loose the rule in this case.
|
|
||||||
if (process.env.NODE_ENV !== 'test' || process.env.MISSKEY_TEST_CHECK_IP_RANGE === '1') {
|
if (process.env.NODE_ENV !== 'test' || process.env.MISSKEY_TEST_CHECK_IP_RANGE === '1') {
|
||||||
const lookup = await dns.lookup(clientUrl.hostname);
|
const lookup = await dns.lookup(clientUrl.hostname);
|
||||||
if (ipaddr.parse(lookup.address).range() !== 'unicast') {
|
if (ipaddr.parse(lookup.address).range() !== 'unicast') {
|
||||||
|
@ -353,6 +362,9 @@ export class OAuth2ProviderService {
|
||||||
|
|
||||||
// Find client information from the remote.
|
// Find client information from the remote.
|
||||||
const clientInfo = await discoverClientInformation(this.httpRequestService, clientUrl.href);
|
const clientInfo = await discoverClientInformation(this.httpRequestService, clientUrl.href);
|
||||||
|
|
||||||
|
// Requires an explicit list of redirect_uris per
|
||||||
|
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.1.3
|
||||||
if (!clientInfo.redirectUris.includes(redirectURI)) {
|
if (!clientInfo.redirectUris.includes(redirectURI)) {
|
||||||
throw new AuthorizationError('Invalid redirect_uri', 'invalid_request');
|
throw new AuthorizationError('Invalid redirect_uri', 'invalid_request');
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue