tmp
This commit is contained in:
parent
a4fb17620c
commit
f5a6509663
3 changed files with 333 additions and 171 deletions
|
@ -90,6 +90,7 @@
|
||||||
"date-fns": "2.30.0",
|
"date-fns": "2.30.0",
|
||||||
"deep-email-validator": "0.1.21",
|
"deep-email-validator": "0.1.21",
|
||||||
"escape-regexp": "0.0.1",
|
"escape-regexp": "0.0.1",
|
||||||
|
"express-session": "^1.17.3",
|
||||||
"fastify": "4.18.0",
|
"fastify": "4.18.0",
|
||||||
"feed": "4.2.2",
|
"feed": "4.2.2",
|
||||||
"file-type": "18.5.0",
|
"file-type": "18.5.0",
|
||||||
|
@ -117,6 +118,8 @@
|
||||||
"nodemailer": "6.9.3",
|
"nodemailer": "6.9.3",
|
||||||
"nsfwjs": "2.4.2",
|
"nsfwjs": "2.4.2",
|
||||||
"oauth": "0.10.0",
|
"oauth": "0.10.0",
|
||||||
|
"oauth2orize": "^1.11.1",
|
||||||
|
"oauth2orize-pkce": "^0.1.2",
|
||||||
"oidc-provider": "^8.1.1",
|
"oidc-provider": "^8.1.1",
|
||||||
"os-utils": "0.0.14",
|
"os-utils": "0.0.14",
|
||||||
"otpauth": "9.1.2",
|
"otpauth": "9.1.2",
|
||||||
|
@ -171,6 +174,7 @@
|
||||||
"@types/color-convert": "2.0.0",
|
"@types/color-convert": "2.0.0",
|
||||||
"@types/content-disposition": "0.5.5",
|
"@types/content-disposition": "0.5.5",
|
||||||
"@types/escape-regexp": "0.0.1",
|
"@types/escape-regexp": "0.0.1",
|
||||||
|
"@types/express-session": "^1.17.6",
|
||||||
"@types/fluent-ffmpeg": "2.1.21",
|
"@types/fluent-ffmpeg": "2.1.21",
|
||||||
"@types/jest": "29.5.2",
|
"@types/jest": "29.5.2",
|
||||||
"@types/js-yaml": "4.0.5",
|
"@types/js-yaml": "4.0.5",
|
||||||
|
@ -183,6 +187,7 @@
|
||||||
"@types/node-fetch": "3.0.3",
|
"@types/node-fetch": "3.0.3",
|
||||||
"@types/nodemailer": "6.4.8",
|
"@types/nodemailer": "6.4.8",
|
||||||
"@types/oauth": "0.9.1",
|
"@types/oauth": "0.9.1",
|
||||||
|
"@types/oauth2orize": "^1.8.11",
|
||||||
"@types/pg": "8.10.2",
|
"@types/pg": "8.10.2",
|
||||||
"@types/pug": "2.0.6",
|
"@types/pug": "2.0.6",
|
||||||
"@types/punycode": "2.1.0",
|
"@types/punycode": "2.1.0",
|
||||||
|
|
|
@ -1,17 +1,28 @@
|
||||||
import dns from 'node:dns/promises';
|
import dns from 'node:dns/promises';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import Provider, { type Adapter, type Account, AdapterPayload } from 'oidc-provider';
|
import Provider, { type Adapter, type Account, AdapterPayload } from 'oidc-provider';
|
||||||
import fastifyMiddie from '@fastify/middie';
|
import fastifyMiddie, { IncomingMessageExtended } from '@fastify/middie';
|
||||||
import { JSDOM } from 'jsdom';
|
import { JSDOM } from 'jsdom';
|
||||||
import parseLinkHeader from 'parse-link-header';
|
import parseLinkHeader from 'parse-link-header';
|
||||||
import ipaddr from 'ipaddr.js';
|
import ipaddr from 'ipaddr.js';
|
||||||
|
import oauth2orize from 'oauth2orize';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { kinds } from '@/misc/api-permissions.js';
|
import { kinds } from '@/misc/api-permissions.js';
|
||||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||||
import type { FastifyInstance } from 'fastify';
|
import type { FastifyInstance } from 'fastify';
|
||||||
|
import fastifyCookie from '@fastify/cookie';
|
||||||
|
import fastifySession from '@fastify/session';
|
||||||
import type Redis from 'ioredis';
|
import type Redis from 'ioredis';
|
||||||
|
import oauth2Pkce from 'oauth2orize-pkce';
|
||||||
|
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||||
|
import expressSession from 'express-session';
|
||||||
|
import http from 'node:http';
|
||||||
|
import fastifyView from '@fastify/view';
|
||||||
|
import pug from 'pug';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
|
||||||
// https://indieauth.spec.indieweb.org/#client-identifier
|
// https://indieauth.spec.indieweb.org/#client-identifier
|
||||||
function validateClientId(raw: string): URL {
|
function validateClientId(raw: string): URL {
|
||||||
|
@ -63,32 +74,32 @@ function validateClientId(raw: string): URL {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
const grantable = new Set([
|
// const grantable = new Set([
|
||||||
'AccessToken',
|
// 'AccessToken',
|
||||||
'AuthorizationCode',
|
// 'AuthorizationCode',
|
||||||
'RefreshToken',
|
// 'RefreshToken',
|
||||||
'DeviceCode',
|
// 'DeviceCode',
|
||||||
'BackchannelAuthenticationRequest',
|
// 'BackchannelAuthenticationRequest',
|
||||||
]);
|
// ]);
|
||||||
|
|
||||||
const consumable = new Set([
|
// const consumable = new Set([
|
||||||
'AuthorizationCode',
|
// 'AuthorizationCode',
|
||||||
'RefreshToken',
|
// 'RefreshToken',
|
||||||
'DeviceCode',
|
// 'DeviceCode',
|
||||||
'BackchannelAuthenticationRequest',
|
// 'BackchannelAuthenticationRequest',
|
||||||
]);
|
// ]);
|
||||||
|
|
||||||
function grantKeyFor(id: string): string {
|
// function grantKeyFor(id: string): string {
|
||||||
return `grant:${id}`;
|
// return `grant:${id}`;
|
||||||
}
|
// }
|
||||||
|
|
||||||
function userCodeKeyFor(userCode: string): string {
|
// function userCodeKeyFor(userCode: string): string {
|
||||||
return `userCode:${userCode}`;
|
// return `userCode:${userCode}`;
|
||||||
}
|
// }
|
||||||
|
|
||||||
function uidKeyFor(uid: string): string {
|
// function uidKeyFor(uid: string): string {
|
||||||
return `uid:${uid}`;
|
// return `uid:${uid}`;
|
||||||
}
|
// }
|
||||||
|
|
||||||
async function fetchFromClientId(httpRequestService: HttpRequestService, id: string): Promise<string | void> {
|
async function fetchFromClientId(httpRequestService: HttpRequestService, id: string): Promise<string | void> {
|
||||||
try {
|
try {
|
||||||
|
@ -107,179 +118,201 @@ async function fetchFromClientId(httpRequestService: HttpRequestService, id: str
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MisskeyAdapter implements Adapter {
|
// class MisskeyAdapter implements Adapter {
|
||||||
name = 'oauth2';
|
// name = 'oauth2';
|
||||||
|
|
||||||
constructor(private redisClient: Redis.Redis, private httpRequestService: HttpRequestService) { }
|
// constructor(private redisClient: Redis.Redis, private httpRequestService: HttpRequestService) { }
|
||||||
|
|
||||||
key(id: string): string {
|
// key(id: string): string {
|
||||||
return `oauth2:${id}`;
|
// return `oauth2:${id}`;
|
||||||
}
|
// }
|
||||||
|
|
||||||
async upsert(id: string, payload: AdapterPayload, expiresIn: number): Promise<void> {
|
// async upsert(id: string, payload: AdapterPayload, expiresIn: number): Promise<void> {
|
||||||
console.log('oauth upsert', id, payload, expiresIn);
|
// console.log('oauth upsert', id, payload, expiresIn);
|
||||||
|
|
||||||
const key = this.key(id);
|
// const key = this.key(id);
|
||||||
|
|
||||||
const multi = this.redisClient.multi();
|
// const multi = this.redisClient.multi();
|
||||||
if (consumable.has(this.name)) {
|
// if (consumable.has(this.name)) {
|
||||||
multi.hset(key, { payload: JSON.stringify(payload) });
|
// multi.hset(key, { payload: JSON.stringify(payload) });
|
||||||
} else {
|
// } else {
|
||||||
multi.set(key, JSON.stringify(payload));
|
// multi.set(key, JSON.stringify(payload));
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (expiresIn) {
|
// if (expiresIn) {
|
||||||
multi.expire(key, expiresIn);
|
// multi.expire(key, expiresIn);
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (grantable.has(this.name) && payload.grantId) {
|
// if (grantable.has(this.name) && payload.grantId) {
|
||||||
const grantKey = grantKeyFor(payload.grantId);
|
// const grantKey = grantKeyFor(payload.grantId);
|
||||||
multi.rpush(grantKey, key);
|
// multi.rpush(grantKey, key);
|
||||||
// if you're seeing grant key lists growing out of acceptable proportions consider using LTRIM
|
// // if you're seeing grant key lists growing out of acceptable proportions consider using LTRIM
|
||||||
// here to trim the list to an appropriate length
|
// // here to trim the list to an appropriate length
|
||||||
const ttl = await this.redisClient.ttl(grantKey);
|
// const ttl = await this.redisClient.ttl(grantKey);
|
||||||
if (expiresIn > ttl) {
|
// if (expiresIn > ttl) {
|
||||||
multi.expire(grantKey, expiresIn);
|
// multi.expire(grantKey, expiresIn);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (payload.userCode) {
|
// if (payload.userCode) {
|
||||||
const userCodeKey = userCodeKeyFor(payload.userCode);
|
// const userCodeKey = userCodeKeyFor(payload.userCode);
|
||||||
multi.set(userCodeKey, id);
|
// multi.set(userCodeKey, id);
|
||||||
multi.expire(userCodeKey, expiresIn);
|
// multi.expire(userCodeKey, expiresIn);
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (payload.uid) {
|
// if (payload.uid) {
|
||||||
const uidKey = uidKeyFor(payload.uid);
|
// const uidKey = uidKeyFor(payload.uid);
|
||||||
multi.set(uidKey, id);
|
// multi.set(uidKey, id);
|
||||||
multi.expire(uidKey, expiresIn);
|
// multi.expire(uidKey, expiresIn);
|
||||||
}
|
// }
|
||||||
|
|
||||||
await multi.exec();
|
// await multi.exec();
|
||||||
}
|
// }
|
||||||
|
|
||||||
async find(id: string): Promise<void | AdapterPayload> {
|
// async find(id: string): Promise<void | AdapterPayload> {
|
||||||
console.log('oauth find', id);
|
// console.log('oauth find', id);
|
||||||
|
|
||||||
// XXX: really?
|
// // XXX: really?
|
||||||
const fromRedis = await this.findRedis(id);
|
// const fromRedis = await this.findRedis(id);
|
||||||
if (fromRedis) {
|
// if (fromRedis) {
|
||||||
return fromRedis;
|
// return fromRedis;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Find client information from the remote.
|
// // Find client information from the remote.
|
||||||
const url = validateClientId(id);
|
// const url = validateClientId(id);
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'test') {
|
// if (process.env.NODE_ENV !== 'test') {
|
||||||
const lookup = await dns.lookup(url.hostname);
|
// const lookup = await dns.lookup(url.hostname);
|
||||||
if (ipaddr.parse(lookup.address).range() === 'loopback') {
|
// if (ipaddr.parse(lookup.address).range() === 'loopback') {
|
||||||
throw new Error('client_id unexpectedly resolves to loopback IP.');
|
// throw new Error('client_id unexpectedly resolves to loopback IP.');
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
const redirectUri = await fetchFromClientId(this.httpRequestService, id);
|
// const redirectUri = await fetchFromClientId(this.httpRequestService, id);
|
||||||
if (!redirectUri) {
|
// if (!redirectUri) {
|
||||||
// IndieAuth also implicitly allows any path under the same scheme+host,
|
// // IndieAuth also implicitly allows any path under the same scheme+host,
|
||||||
// but oidc-provider requires explicit list of uris.
|
// // but oidc-provider requires explicit list of uris.
|
||||||
throw new Error('The URL of client_id must provide `redirect_uri` as HTTP Link header or HTML <link> element.');
|
// throw new Error('The URL of client_id must provide `redirect_uri` as HTTP Link header or HTML <link> element.');
|
||||||
}
|
// }
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
client_id: id,
|
// client_id: id,
|
||||||
token_endpoint_auth_method: 'none',
|
// token_endpoint_auth_method: 'none',
|
||||||
redirect_uris: [redirectUri],
|
// redirect_uris: [redirectUri],
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
async findRedis(id: string | null): Promise<void | AdapterPayload> {
|
// async findRedis(id: string | null): Promise<void | AdapterPayload> {
|
||||||
if (!id) {
|
// if (!id) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const data = consumable.has(this.name)
|
// const data = consumable.has(this.name)
|
||||||
? await this.redisClient.hgetall(this.key(id))
|
// ? await this.redisClient.hgetall(this.key(id))
|
||||||
: await this.redisClient.get(this.key(id));
|
// : await this.redisClient.get(this.key(id));
|
||||||
|
|
||||||
if (!data || (typeof data === 'object' && !Object.entries(data).length)) {
|
// if (!data || (typeof data === 'object' && !Object.entries(data).length)) {
|
||||||
return undefined;
|
// return undefined;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (typeof data === 'string') {
|
// if (typeof data === 'string') {
|
||||||
return JSON.parse(data);
|
// return JSON.parse(data);
|
||||||
}
|
// }
|
||||||
const { payload, ...rest } = data as any;
|
// const { payload, ...rest } = data as any;
|
||||||
return {
|
// return {
|
||||||
...rest,
|
// ...rest,
|
||||||
...JSON.parse(payload),
|
// ...JSON.parse(payload),
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
async findByUserCode(userCode: string): Promise<void | AdapterPayload> {
|
// async findByUserCode(userCode: string): Promise<void | AdapterPayload> {
|
||||||
console.log('oauth findByUserCode', userCode);
|
// console.log('oauth findByUserCode', userCode);
|
||||||
const id = await this.redisClient.get(userCodeKeyFor(userCode));
|
// const id = await this.redisClient.get(userCodeKeyFor(userCode));
|
||||||
return this.findRedis(id);
|
// return this.findRedis(id);
|
||||||
}
|
// }
|
||||||
|
|
||||||
async findByUid(uid: string): Promise<void | AdapterPayload> {
|
// async findByUid(uid: string): Promise<void | AdapterPayload> {
|
||||||
console.log('oauth findByUid', uid);
|
// console.log('oauth findByUid', uid);
|
||||||
const id = await this.redisClient.get(uidKeyFor(uid));
|
// const id = await this.redisClient.get(uidKeyFor(uid));
|
||||||
return this.findRedis(id);
|
// return this.findRedis(id);
|
||||||
}
|
// }
|
||||||
|
|
||||||
async consume(id: string): Promise<void> {
|
// async consume(id: string): Promise<void> {
|
||||||
console.log('oauth consume', id);
|
// console.log('oauth consume', id);
|
||||||
await this.redisClient.hset(this.key(id), 'consumed', Math.floor(Date.now() / 1000));
|
// await this.redisClient.hset(this.key(id), 'consumed', Math.floor(Date.now() / 1000));
|
||||||
}
|
// }
|
||||||
|
|
||||||
async destroy(id: string): Promise<void | undefined> {
|
// async destroy(id: string): Promise<void | undefined> {
|
||||||
console.log('oauth destroy', id);
|
// console.log('oauth destroy', id);
|
||||||
const key = this.key(id);
|
// const key = this.key(id);
|
||||||
await this.redisClient.del(key);
|
// await this.redisClient.del(key);
|
||||||
}
|
// }
|
||||||
|
|
||||||
async revokeByGrantId(grantId: string): Promise<void | undefined> {
|
// async revokeByGrantId(grantId: string): Promise<void | undefined> {
|
||||||
console.log('oauth revokeByGrandId', grantId);
|
// console.log('oauth revokeByGrandId', grantId);
|
||||||
const multi = this.redisClient.multi();
|
// const multi = this.redisClient.multi();
|
||||||
const tokens = await this.redisClient.lrange(grantKeyFor(grantId), 0, -1);
|
// const tokens = await this.redisClient.lrange(grantKeyFor(grantId), 0, -1);
|
||||||
tokens.forEach((token) => multi.del(token));
|
// tokens.forEach((token) => multi.del(token));
|
||||||
multi.del(grantKeyFor(grantId));
|
// multi.del(grantKeyFor(grantId));
|
||||||
await multi.exec();
|
// await multi.exec();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
// function promisify<T>(callback: T) {
|
||||||
|
// return (...args: Parameters<T>) => {
|
||||||
|
|
||||||
|
// args[args.length - 1]();
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
type OmitFirstElement<T extends unknown[]> = T extends [unknown, ...(infer R)]
|
||||||
|
? R
|
||||||
|
: [];
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OAuth2ProviderService {
|
export class OAuth2ProviderService {
|
||||||
#provider: Provider;
|
// #provider: Provider;
|
||||||
|
#server = oauth2orize.createServer();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
@Inject(DI.redis) redisClient: Redis.Redis,
|
@Inject(DI.redis)
|
||||||
httpRequestService: HttpRequestService,
|
private redisClient: Redis.Redis,
|
||||||
|
private httpRequestService: HttpRequestService,
|
||||||
|
private metaService: MetaService,
|
||||||
) {
|
) {
|
||||||
this.#provider = new Provider(config.url, {
|
// this.#provider = new Provider(config.url, {
|
||||||
clientAuthMethods: ['none'],
|
// clientAuthMethods: ['none'],
|
||||||
pkce: {
|
// pkce: {
|
||||||
// This is the default, but be explicit here as we announce it below
|
// // This is the default, but be explicit here as we announce it below
|
||||||
methods: ['S256'],
|
// methods: ['S256'],
|
||||||
},
|
// },
|
||||||
routes: {
|
// routes: {
|
||||||
// defaults to '/auth' but '/authorize' is more consistent with many
|
// // defaults to '/auth' but '/authorize' is more consistent with many
|
||||||
// other services eg. Mastodon/Twitter/Facebook/GitLab/GitHub/etc.
|
// // other services eg. Mastodon/Twitter/Facebook/GitLab/GitHub/etc.
|
||||||
authorization: '/authorize',
|
// authorization: '/authorize',
|
||||||
},
|
// },
|
||||||
scopes: kinds,
|
// scopes: kinds,
|
||||||
async findAccount(ctx, id): Promise<Account | undefined> {
|
// async findAccount(ctx, id): Promise<Account | undefined> {
|
||||||
console.log(id);
|
// console.log(id);
|
||||||
return undefined;
|
// return undefined;
|
||||||
},
|
// },
|
||||||
adapter(): MisskeyAdapter {
|
// adapter(): MisskeyAdapter {
|
||||||
return new MisskeyAdapter(redisClient, httpRequestService);
|
// return new MisskeyAdapter(redisClient, httpRequestService);
|
||||||
},
|
// },
|
||||||
async renderError(ctx, out, error): Promise<void> {
|
// async renderError(ctx, out, error): Promise<void> {
|
||||||
console.log(error);
|
// console.log(error);
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
|
this.#server.grant(oauth2Pkce.extensions());
|
||||||
|
this.#server.grant(oauth2orize.grant.code((client, redirectUri, user, ares, done) => {
|
||||||
|
console.log(client, redirectUri, user, ares);
|
||||||
|
const code = secureRndstr(32, true);
|
||||||
|
done(null, code);
|
||||||
|
}));
|
||||||
|
this.#server.serializeClient((client, done) => done(null, client));
|
||||||
|
this.#server.deserializeClient((id, done) => done(null, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return 404 for any unknown paths under /oauth so that clients can know
|
// Return 404 for any unknown paths under /oauth so that clients can know
|
||||||
|
@ -316,12 +349,65 @@ export class OAuth2ProviderService {
|
||||||
// no way to turn it off.
|
// no way to turn it off.
|
||||||
// For now only allow the basic OAuth endpoints, to start small and evaluate
|
// For now only allow the basic OAuth endpoints, to start small and evaluate
|
||||||
// this feature for some time, given that this is security related.
|
// this feature for some time, given that this is security related.
|
||||||
fastify.get('/oauth/authorize', async () => { });
|
fastify.get<{ Querystring: { code_challenge?: string, code_challenge_method?: string } }>('/oauth/authorize', async (request, reply) => {
|
||||||
|
console.log('HIT /oauth/authorize', request.query);
|
||||||
|
if (typeof request.query.code_challenge !== 'string') {
|
||||||
|
throw new Error('`code_challenge` parameter is required');
|
||||||
|
}
|
||||||
|
if (request.query.code_challenge_method !== 'S256') {
|
||||||
|
throw new Error('`code_challenge_method` parameter must be set as S256');
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = await this.metaService.fetch();
|
||||||
|
return await reply.view('base', {
|
||||||
|
img: meta.bannerUrl,
|
||||||
|
title: meta.name ?? 'Misskey',
|
||||||
|
instanceName: meta.name ?? 'Misskey',
|
||||||
|
url: this.config.url,
|
||||||
|
desc: meta.description,
|
||||||
|
icon: meta.iconUrl,
|
||||||
|
themeColor: meta.themeColor,
|
||||||
|
});
|
||||||
|
});
|
||||||
fastify.post('/oauth/token', async () => { });
|
fastify.post('/oauth/token', async () => { });
|
||||||
fastify.get('/oauth/interaction/:uid', async () => { });
|
// fastify.get('/oauth/interaction/:uid', async () => { });
|
||||||
fastify.get('/oauth/interaction/:uid/login', async () => { });
|
// fastify.get('/oauth/interaction/:uid/login', async () => { });
|
||||||
|
|
||||||
|
fastify.register(fastifyView, {
|
||||||
|
root: fileURLToPath(new URL('../web/views', import.meta.url)),
|
||||||
|
engine: { pug },
|
||||||
|
defaultContext: {
|
||||||
|
version: this.config.version,
|
||||||
|
config: this.config,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await fastify.register(fastifyMiddie);
|
await fastify.register(fastifyMiddie);
|
||||||
fastify.use('/oauth', this.#provider.callback());
|
fastify.use(expressSession({ secret: 'keyboard cat', resave: false, saveUninitialized: false }) as any);
|
||||||
|
fastify.use('/oauth/authorize', this.#server.authorization((clientId, redirectUri, done) => {
|
||||||
|
(async (): Promise<OmitFirstElement<Parameters<typeof done>>> => {
|
||||||
|
console.log('HIT /oauth/authorize validation middleware');
|
||||||
|
|
||||||
|
// Find client information from the remote.
|
||||||
|
const clientUrl = validateClientId(clientId);
|
||||||
|
const redirectUrl = new URL(redirectUri);
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'test') {
|
||||||
|
const lookup = await dns.lookup(clientUrl.hostname);
|
||||||
|
if (ipaddr.parse(lookup.address).range() === 'loopback') {
|
||||||
|
throw new Error('client_id unexpectedly resolves to loopback IP.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (redirectUrl.protocol !== clientUrl.protocol || redirectUrl.host !== clientUrl.host) {
|
||||||
|
// TODO: allow more redirect_uri by Client Information Discovery
|
||||||
|
throw new Error('cross-origin redirect_uri is not supported yet.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return [clientId, redirectUri];
|
||||||
|
})().then(args => done(null, ...args), err => done(err));
|
||||||
|
}));
|
||||||
|
|
||||||
|
// fastify.use('/oauth', this.#provider.callback());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,6 +185,9 @@ importers:
|
||||||
escape-regexp:
|
escape-regexp:
|
||||||
specifier: 0.0.1
|
specifier: 0.0.1
|
||||||
version: 0.0.1
|
version: 0.0.1
|
||||||
|
express-session:
|
||||||
|
specifier: ^1.17.3
|
||||||
|
version: 1.17.3
|
||||||
fastify:
|
fastify:
|
||||||
specifier: 4.18.0
|
specifier: 4.18.0
|
||||||
version: 4.18.0
|
version: 4.18.0
|
||||||
|
@ -266,6 +269,12 @@ importers:
|
||||||
oauth:
|
oauth:
|
||||||
specifier: 0.10.0
|
specifier: 0.10.0
|
||||||
version: 0.10.0
|
version: 0.10.0
|
||||||
|
oauth2orize:
|
||||||
|
specifier: ^1.11.1
|
||||||
|
version: 1.11.1
|
||||||
|
oauth2orize-pkce:
|
||||||
|
specifier: ^0.1.2
|
||||||
|
version: 0.1.2
|
||||||
oidc-provider:
|
oidc-provider:
|
||||||
specifier: ^8.1.1
|
specifier: ^8.1.1
|
||||||
version: 8.1.1
|
version: 8.1.1
|
||||||
|
@ -505,6 +514,9 @@ importers:
|
||||||
'@types/escape-regexp':
|
'@types/escape-regexp':
|
||||||
specifier: 0.0.1
|
specifier: 0.0.1
|
||||||
version: 0.0.1
|
version: 0.0.1
|
||||||
|
'@types/express-session':
|
||||||
|
specifier: ^1.17.6
|
||||||
|
version: 1.17.6
|
||||||
'@types/fluent-ffmpeg':
|
'@types/fluent-ffmpeg':
|
||||||
specifier: 2.1.21
|
specifier: 2.1.21
|
||||||
version: 2.1.21
|
version: 2.1.21
|
||||||
|
@ -541,6 +553,9 @@ importers:
|
||||||
'@types/oauth':
|
'@types/oauth':
|
||||||
specifier: 0.9.1
|
specifier: 0.9.1
|
||||||
version: 0.9.1
|
version: 0.9.1
|
||||||
|
'@types/oauth2orize':
|
||||||
|
specifier: ^1.8.11
|
||||||
|
version: 1.8.11
|
||||||
'@types/pg':
|
'@types/pg':
|
||||||
specifier: 8.10.2
|
specifier: 8.10.2
|
||||||
version: 8.10.2
|
version: 8.10.2
|
||||||
|
@ -7562,6 +7577,12 @@ packages:
|
||||||
'@types/range-parser': 1.2.4
|
'@types/range-parser': 1.2.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/express-session@1.17.6:
|
||||||
|
resolution: {integrity: sha512-L6sB04HVA4HEZo1hDL65JXdZdBJtzZnCiw/P7MnO4w6746tJCNtXlHtzEASyI9ccn9zyOw6IbqQuhVa03VpO4w==}
|
||||||
|
dependencies:
|
||||||
|
'@types/express': 4.17.17
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/express@4.17.17:
|
/@types/express@4.17.17:
|
||||||
resolution: {integrity: sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==}
|
resolution: {integrity: sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -7782,6 +7803,13 @@ packages:
|
||||||
resolution: {integrity: sha512-WKG4gTr8przEZBiJ5r3s8ZIAoMXNbOgQ+j/d5O4X3x6kZJRLNvyUJuUK/KoG3+8BaOHPhp2m7WC6JKKeovDSzQ==}
|
resolution: {integrity: sha512-WKG4gTr8przEZBiJ5r3s8ZIAoMXNbOgQ+j/d5O4X3x6kZJRLNvyUJuUK/KoG3+8BaOHPhp2m7WC6JKKeovDSzQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/oauth2orize@1.8.11:
|
||||||
|
resolution: {integrity: sha512-eir5IGegpcnPuhnx1Asdxj3kDWWP/Qr1qkERMlDASwlEJM6pppVBxkW7ZvAX2H8eBHE+FP7lhg/iNlRrtNGewQ==}
|
||||||
|
dependencies:
|
||||||
|
'@types/express': 4.17.17
|
||||||
|
'@types/node': 20.3.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/oauth@0.9.1:
|
/@types/oauth@0.9.1:
|
||||||
resolution: {integrity: sha512-a1iY62/a3yhZ7qH7cNUsxoI3U/0Fe9+RnuFrpTKr+0WVOzbKlSLojShCKe20aOD1Sppv+i8Zlq0pLDuTJnwS4A==}
|
resolution: {integrity: sha512-a1iY62/a3yhZ7qH7cNUsxoI3U/0Fe9+RnuFrpTKr+0WVOzbKlSLojShCKe20aOD1Sppv+i8Zlq0pLDuTJnwS4A==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -10434,12 +10462,10 @@ packages:
|
||||||
|
|
||||||
/cookie-signature@1.0.6:
|
/cookie-signature@1.0.6:
|
||||||
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
|
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/cookie@0.4.2:
|
/cookie@0.4.2:
|
||||||
resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
|
resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/cookie@0.5.0:
|
/cookie@0.5.0:
|
||||||
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
|
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
|
||||||
|
@ -11847,6 +11873,22 @@ packages:
|
||||||
resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==}
|
resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/express-session@1.17.3:
|
||||||
|
resolution: {integrity: sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==}
|
||||||
|
engines: {node: '>= 0.8.0'}
|
||||||
|
dependencies:
|
||||||
|
cookie: 0.4.2
|
||||||
|
cookie-signature: 1.0.6
|
||||||
|
debug: 2.6.9
|
||||||
|
depd: 2.0.0
|
||||||
|
on-headers: 1.0.2
|
||||||
|
parseurl: 1.3.3
|
||||||
|
safe-buffer: 5.2.1
|
||||||
|
uid-safe: 2.1.5
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
/express@4.18.2:
|
/express@4.18.2:
|
||||||
resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==}
|
resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==}
|
||||||
engines: {node: '>= 0.10.0'}
|
engines: {node: '>= 0.10.0'}
|
||||||
|
@ -16276,6 +16318,21 @@ packages:
|
||||||
resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==}
|
resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/oauth2orize-pkce@0.1.2:
|
||||||
|
resolution: {integrity: sha512-grto2UYhXHi9GLE3IBgBBbV87xci55+bCyjpVuxKyzol6I5Rg0K1MiTuXE+JZk54R86SG2wqXODMiZYHraPpxw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/oauth2orize@1.11.1:
|
||||||
|
resolution: {integrity: sha512-9dSx/Gwm0J2Rvj4RH9+h7iXVnRXZ6biwWRgb2dCeQhCosODS0nYdM9I/G7BUGsjbgn0pHjGcn1zcCRtzj2SlRA==}
|
||||||
|
engines: {node: '>= 0.4.0'}
|
||||||
|
dependencies:
|
||||||
|
debug: 2.6.9
|
||||||
|
uid2: 0.0.4
|
||||||
|
utils-merge: 1.0.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
/oauth@0.10.0:
|
/oauth@0.10.0:
|
||||||
resolution: {integrity: sha512-1orQ9MT1vHFGQxhuy7E/0gECD3fd2fCC+PIX+/jgmU/gI3EpRocXtmtvxCO5x3WZ443FLTLFWNDjl5MPJf9u+Q==}
|
resolution: {integrity: sha512-1orQ9MT1vHFGQxhuy7E/0gECD3fd2fCC+PIX+/jgmU/gI3EpRocXtmtvxCO5x3WZ443FLTLFWNDjl5MPJf9u+Q==}
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -16427,7 +16484,6 @@ packages:
|
||||||
/on-headers@1.0.2:
|
/on-headers@1.0.2:
|
||||||
resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==}
|
resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/once@1.4.0:
|
/once@1.4.0:
|
||||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
|
@ -17777,6 +17833,11 @@ packages:
|
||||||
resolution: {integrity: sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==}
|
resolution: {integrity: sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/random-bytes@1.0.0:
|
||||||
|
resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/random-seed@0.3.0:
|
/random-seed@0.3.0:
|
||||||
resolution: {integrity: sha512-y13xtn3kcTlLub3HKWXxJNeC2qK4mB59evwZ5EkeRlolx+Bp2ztF7LbcZmyCnOqlHQrLnfuNbi1sVmm9lPDlDA==}
|
resolution: {integrity: sha512-y13xtn3kcTlLub3HKWXxJNeC2qK4mB59evwZ5EkeRlolx+Bp2ztF7LbcZmyCnOqlHQrLnfuNbi1sVmm9lPDlDA==}
|
||||||
engines: {node: '>= 0.6.0'}
|
engines: {node: '>= 0.6.0'}
|
||||||
|
@ -20173,6 +20234,17 @@ packages:
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/uid-safe@2.1.5:
|
||||||
|
resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dependencies:
|
||||||
|
random-bytes: 1.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/uid2@0.0.4:
|
||||||
|
resolution: {integrity: sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/uid@2.0.2:
|
/uid@2.0.2:
|
||||||
resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==}
|
resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
@ -20453,7 +20525,6 @@ packages:
|
||||||
/utils-merge@1.0.1:
|
/utils-merge@1.0.1:
|
||||||
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
|
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
|
||||||
engines: {node: '>= 0.4.0'}
|
engines: {node: '>= 0.4.0'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/uuid@3.4.0:
|
/uuid@3.4.0:
|
||||||
resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==}
|
resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==}
|
||||||
|
|
Loading…
Reference in a new issue