feat(backend/oauth): allow CORS for token endpoint (#12814)
* feat(backend/oauth): allow CORS for token endpoint * no need to explicitly set origin to `*` * Update CHANGELOG.md
This commit is contained in:
parent
c96bc36fed
commit
ad346b6f36
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -12,6 +12,17 @@
|
||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
## 2023.x.x (unreleased)
|
||||||
|
|
||||||
|
### General
|
||||||
|
-
|
||||||
|
|
||||||
|
### Client
|
||||||
|
-
|
||||||
|
|
||||||
|
### Server
|
||||||
|
- Enhance: `oauth/token`エンドポイントのCORS対応
|
||||||
|
|
||||||
## 2023.12.1
|
## 2023.12.1
|
||||||
|
|
||||||
### General
|
### General
|
||||||
|
|
|
@ -68,7 +68,7 @@
|
||||||
"@discordapp/twemoji": "15.0.2",
|
"@discordapp/twemoji": "15.0.2",
|
||||||
"@fastify/accepts": "4.3.0",
|
"@fastify/accepts": "4.3.0",
|
||||||
"@fastify/cookie": "9.2.0",
|
"@fastify/cookie": "9.2.0",
|
||||||
"@fastify/cors": "8.4.2",
|
"@fastify/cors": "8.5.0",
|
||||||
"@fastify/express": "2.3.0",
|
"@fastify/express": "2.3.0",
|
||||||
"@fastify/http-proxy": "9.3.0",
|
"@fastify/http-proxy": "9.3.0",
|
||||||
"@fastify/multipart": "8.0.0",
|
"@fastify/multipart": "8.0.0",
|
||||||
|
|
|
@ -107,7 +107,8 @@ export class ServerService implements OnApplicationShutdown {
|
||||||
fastify.register(this.activityPubServerService.createServer);
|
fastify.register(this.activityPubServerService.createServer);
|
||||||
fastify.register(this.nodeinfoServerService.createServer);
|
fastify.register(this.nodeinfoServerService.createServer);
|
||||||
fastify.register(this.wellKnownServerService.createServer);
|
fastify.register(this.wellKnownServerService.createServer);
|
||||||
fastify.register(this.oauth2ProviderService.createServer);
|
fastify.register(this.oauth2ProviderService.createServer, { prefix: '/oauth' });
|
||||||
|
fastify.register(this.oauth2ProviderService.createTokenServer, { prefix: '/oauth/token' });
|
||||||
|
|
||||||
fastify.get<{ Params: { path: string }; Querystring: { static?: any; badge?: any; }; }>('/emoji/:path(.*)', async (request, reply) => {
|
fastify.get<{ Params: { path: string }; Querystring: { static?: any; badge?: any; }; }>('/emoji/:path(.*)', async (request, reply) => {
|
||||||
const path = request.params.path;
|
const path = request.params.path;
|
||||||
|
|
|
@ -16,6 +16,7 @@ import * as Acct from '@/misc/acct.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { NodeinfoServerService } from './NodeinfoServerService.js';
|
import { NodeinfoServerService } from './NodeinfoServerService.js';
|
||||||
|
import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
|
||||||
import type { FindOptionsWhere } from 'typeorm';
|
import type { FindOptionsWhere } from 'typeorm';
|
||||||
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
||||||
|
|
||||||
|
@ -30,6 +31,7 @@ export class WellKnownServerService {
|
||||||
|
|
||||||
private nodeinfoServerService: NodeinfoServerService,
|
private nodeinfoServerService: NodeinfoServerService,
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
|
private oauth2ProviderService: OAuth2ProviderService,
|
||||||
) {
|
) {
|
||||||
//this.createServer = this.createServer.bind(this);
|
//this.createServer = this.createServer.bind(this);
|
||||||
}
|
}
|
||||||
|
@ -87,6 +89,10 @@ export class WellKnownServerService {
|
||||||
return { links: this.nodeinfoServerService.getLinks() };
|
return { links: this.nodeinfoServerService.getLinks() };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fastify.get('/.well-known/oauth-authorization-server', async () => {
|
||||||
|
return this.oauth2ProviderService.generateRFC8414();
|
||||||
|
});
|
||||||
|
|
||||||
/* TODO
|
/* TODO
|
||||||
fastify.get('/.well-known/change-password', async (request, reply) => {
|
fastify.get('/.well-known/change-password', async (request, reply) => {
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,6 +11,7 @@ import httpLinkHeader from 'http-link-header';
|
||||||
import ipaddr from 'ipaddr.js';
|
import ipaddr from 'ipaddr.js';
|
||||||
import oauth2orize, { type OAuth2, AuthorizationError, ValidateFunctionArity2, OAuth2Req, MiddlewareRequest } from 'oauth2orize';
|
import oauth2orize, { type OAuth2, AuthorizationError, ValidateFunctionArity2, OAuth2Req, MiddlewareRequest } from 'oauth2orize';
|
||||||
import oauth2Pkce from 'oauth2orize-pkce';
|
import oauth2Pkce from 'oauth2orize-pkce';
|
||||||
|
import fastifyCors from '@fastify/cors';
|
||||||
import fastifyView from '@fastify/view';
|
import fastifyView from '@fastify/view';
|
||||||
import pug from 'pug';
|
import pug from 'pug';
|
||||||
import bodyParser from 'body-parser';
|
import bodyParser from 'body-parser';
|
||||||
|
@ -348,12 +349,10 @@ export class OAuth2ProviderService {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public async createServer(fastify: FastifyInstance): Promise<void> {
|
|
||||||
// https://datatracker.ietf.org/doc/html/rfc8414.html
|
// https://datatracker.ietf.org/doc/html/rfc8414.html
|
||||||
// https://indieauth.spec.indieweb.org/#indieauth-server-metadata
|
// https://indieauth.spec.indieweb.org/#indieauth-server-metadata
|
||||||
fastify.get('/.well-known/oauth-authorization-server', async (_request, reply) => {
|
public generateRFC8414() {
|
||||||
reply.send({
|
return {
|
||||||
issuer: this.config.url,
|
issuer: this.config.url,
|
||||||
authorization_endpoint: new URL('/oauth/authorize', this.config.url),
|
authorization_endpoint: new URL('/oauth/authorize', this.config.url),
|
||||||
token_endpoint: new URL('/oauth/token', this.config.url),
|
token_endpoint: new URL('/oauth/token', this.config.url),
|
||||||
|
@ -363,10 +362,12 @@ export class OAuth2ProviderService {
|
||||||
service_documentation: 'https://misskey-hub.net',
|
service_documentation: 'https://misskey-hub.net',
|
||||||
code_challenge_methods_supported: ['S256'],
|
code_challenge_methods_supported: ['S256'],
|
||||||
authorization_response_iss_parameter_supported: true,
|
authorization_response_iss_parameter_supported: true,
|
||||||
});
|
};
|
||||||
});
|
}
|
||||||
|
|
||||||
fastify.get('/oauth/authorize', async (request, reply) => {
|
@bindThis
|
||||||
|
public async createServer(fastify: FastifyInstance): Promise<void> {
|
||||||
|
fastify.get('/authorize', async (request, reply) => {
|
||||||
const oauth2 = (request.raw as MiddlewareRequest).oauth2;
|
const oauth2 = (request.raw as MiddlewareRequest).oauth2;
|
||||||
if (!oauth2) {
|
if (!oauth2) {
|
||||||
throw new Error('Unexpected lack of authorization information');
|
throw new Error('Unexpected lack of authorization information');
|
||||||
|
@ -381,8 +382,7 @@ export class OAuth2ProviderService {
|
||||||
scope: oauth2.req.scope.join(' '),
|
scope: oauth2.req.scope.join(' '),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
fastify.post('/oauth/decision', async () => { });
|
fastify.post('/decision', async () => { });
|
||||||
fastify.post('/oauth/token', async () => { });
|
|
||||||
|
|
||||||
fastify.register(fastifyView, {
|
fastify.register(fastifyView, {
|
||||||
root: fileURLToPath(new URL('../web/views', import.meta.url)),
|
root: fileURLToPath(new URL('../web/views', import.meta.url)),
|
||||||
|
@ -394,7 +394,7 @@ export class OAuth2ProviderService {
|
||||||
});
|
});
|
||||||
|
|
||||||
await fastify.register(fastifyExpress);
|
await fastify.register(fastifyExpress);
|
||||||
fastify.use('/oauth/authorize', this.#server.authorize(((areq, done) => {
|
fastify.use('/authorize', this.#server.authorize(((areq, done) => {
|
||||||
(async (): Promise<Parameters<typeof done>> => {
|
(async (): Promise<Parameters<typeof done>> => {
|
||||||
// This should return client/redirectURI AND the error, or
|
// This should return client/redirectURI AND the error, or
|
||||||
// the handler can't send error to the redirection URI
|
// the handler can't send error to the redirection URI
|
||||||
|
@ -448,30 +448,24 @@ export class OAuth2ProviderService {
|
||||||
return [null, clientInfo, redirectURI];
|
return [null, clientInfo, redirectURI];
|
||||||
})().then(args => done(...args), err => done(err));
|
})().then(args => done(...args), err => done(err));
|
||||||
}) as ValidateFunctionArity2));
|
}) as ValidateFunctionArity2));
|
||||||
fastify.use('/oauth/authorize', this.#server.errorHandler({
|
fastify.use('/authorize', this.#server.errorHandler({
|
||||||
mode: 'indirect',
|
mode: 'indirect',
|
||||||
modes: getQueryMode(this.config.url),
|
modes: getQueryMode(this.config.url),
|
||||||
}));
|
}));
|
||||||
fastify.use('/oauth/authorize', this.#server.errorHandler());
|
fastify.use('/authorize', this.#server.errorHandler());
|
||||||
|
|
||||||
fastify.use('/oauth/decision', bodyParser.urlencoded({ extended: false }));
|
fastify.use('/decision', bodyParser.urlencoded({ extended: false }));
|
||||||
fastify.use('/oauth/decision', this.#server.decision((req, done) => {
|
fastify.use('/decision', this.#server.decision((req, done) => {
|
||||||
const { body } = req as OAuth2DecisionRequest;
|
const { body } = req as OAuth2DecisionRequest;
|
||||||
this.#logger.info(`Received the decision. Cancel: ${!!body.cancel}`);
|
this.#logger.info(`Received the decision. Cancel: ${!!body.cancel}`);
|
||||||
req.user = body.login_token;
|
req.user = body.login_token;
|
||||||
done(null, undefined);
|
done(null, undefined);
|
||||||
}));
|
}));
|
||||||
fastify.use('/oauth/decision', this.#server.errorHandler());
|
fastify.use('/decision', this.#server.errorHandler());
|
||||||
|
|
||||||
// Clients may use JSON or urlencoded
|
|
||||||
fastify.use('/oauth/token', bodyParser.urlencoded({ extended: false }));
|
|
||||||
fastify.use('/oauth/token', bodyParser.json({ strict: true }));
|
|
||||||
fastify.use('/oauth/token', this.#server.token());
|
|
||||||
fastify.use('/oauth/token', this.#server.errorHandler());
|
|
||||||
|
|
||||||
// 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
|
||||||
// whether a certain endpoint is supported or not.
|
// whether a certain endpoint is supported or not.
|
||||||
fastify.all('/oauth/*', async (_request, reply) => {
|
fastify.all('/*', async (_request, reply) => {
|
||||||
reply.code(404);
|
reply.code(404);
|
||||||
reply.send({
|
reply.send({
|
||||||
error: {
|
error: {
|
||||||
|
@ -483,4 +477,17 @@ export class OAuth2ProviderService {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async createTokenServer(fastify: FastifyInstance): Promise<void> {
|
||||||
|
fastify.register(fastifyCors);
|
||||||
|
fastify.post('', async () => { });
|
||||||
|
|
||||||
|
await fastify.register(fastifyExpress);
|
||||||
|
// Clients may use JSON or urlencoded
|
||||||
|
fastify.use('', bodyParser.urlencoded({ extended: false }));
|
||||||
|
fastify.use('', bodyParser.json({ strict: true }));
|
||||||
|
fastify.use('', this.#server.token());
|
||||||
|
fastify.use('', this.#server.errorHandler());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
|
import * as assert from 'assert';
|
||||||
|
import { relativeFetch, startServer } from '../utils.js';
|
||||||
|
import type { INestApplicationContext } from '@nestjs/common';
|
||||||
|
|
||||||
|
describe('nodeinfo', () => {
|
||||||
|
let app: INestApplicationContext;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
app = await startServer();
|
||||||
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await app.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('nodeinfo 2.1', async () => {
|
||||||
|
const res = await relativeFetch('nodeinfo/2.1');
|
||||||
|
assert.ok(res.ok);
|
||||||
|
assert.strictEqual(res.headers.get('Access-Control-Allow-Origin'), '*');
|
||||||
|
|
||||||
|
const nodeInfo = await res.json() as any;
|
||||||
|
assert.strictEqual(nodeInfo.software.name, 'misskey');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('nodeinfo 2.0', async () => {
|
||||||
|
const res = await relativeFetch('nodeinfo/2.0');
|
||||||
|
assert.ok(res.ok);
|
||||||
|
assert.strictEqual(res.headers.get('Access-Control-Allow-Origin'), '*');
|
||||||
|
|
||||||
|
const nodeInfo = await res.json() as any;
|
||||||
|
assert.strictEqual(nodeInfo.software.name, 'misskey');
|
||||||
|
});
|
||||||
|
});
|
|
@ -941,4 +941,24 @@ describe('OAuth', () => {
|
||||||
const response = await fetch(new URL('/oauth/foo', host));
|
const response = await fetch(new URL('/oauth/foo', host));
|
||||||
assert.strictEqual(response.status, 404);
|
assert.strictEqual(response.status, 404);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('CORS', () => {
|
||||||
|
test('Token endpoint should support CORS', async () => {
|
||||||
|
const response = await fetch(new URL('/oauth/token', host), { method: 'POST' });
|
||||||
|
assert.ok(!response.ok);
|
||||||
|
assert.strictEqual(response.headers.get('Access-Control-Allow-Origin'), '*');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Authorize endpoint should not support CORS', async () => {
|
||||||
|
const response = await fetch(new URL('/oauth/authorize', host), { method: 'GET' });
|
||||||
|
assert.ok(!response.ok);
|
||||||
|
assert.ok(!response.headers.has('Access-Control-Allow-Origin'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Decision endpoint should not support CORS', async () => {
|
||||||
|
const response = await fetch(new URL('/oauth/decision', host), { method: 'POST' });
|
||||||
|
assert.ok(!response.ok);
|
||||||
|
assert.ok(!response.headers.has('Access-Control-Allow-Origin'));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
|
import * as assert from 'assert';
|
||||||
|
import { host, origin, relativeFetch, signup, startServer } from '../utils.js';
|
||||||
|
import type { INestApplicationContext } from '@nestjs/common';
|
||||||
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
|
describe('.well-known', () => {
|
||||||
|
let app: INestApplicationContext;
|
||||||
|
let alice: misskey.entities.User;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
app = await startServer();
|
||||||
|
|
||||||
|
alice = await signup({ username: 'alice' });
|
||||||
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await app.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('nodeinfo', async () => {
|
||||||
|
const res = await relativeFetch('.well-known/nodeinfo');
|
||||||
|
assert.ok(res.ok);
|
||||||
|
assert.strictEqual(res.headers.get('Access-Control-Allow-Origin'), '*');
|
||||||
|
|
||||||
|
const nodeInfo = await res.json();
|
||||||
|
assert.deepStrictEqual(nodeInfo, {
|
||||||
|
links: [{
|
||||||
|
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1',
|
||||||
|
href: `${origin}/nodeinfo/2.1`,
|
||||||
|
}, {
|
||||||
|
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
|
||||||
|
href: `${origin}/nodeinfo/2.0`,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('webfinger', async () => {
|
||||||
|
const preflight = await relativeFetch(`.well-known/webfinger?resource=acct:alice@${host}`, {
|
||||||
|
method: 'options',
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Request-Method': 'GET',
|
||||||
|
Origin: 'http://example.com',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.ok(preflight.ok);
|
||||||
|
assert.strictEqual(preflight.headers.get('Access-Control-Allow-Headers'), 'Accept');
|
||||||
|
|
||||||
|
const res = await relativeFetch(`.well-known/webfinger?resource=acct:alice@${host}`);
|
||||||
|
assert.ok(res.ok);
|
||||||
|
assert.strictEqual(res.headers.get('Access-Control-Allow-Origin'), '*');
|
||||||
|
assert.strictEqual(res.headers.get('Access-Control-Expose-Headers'), 'Vary');
|
||||||
|
assert.strictEqual(res.headers.get('Vary'), 'Accept');
|
||||||
|
|
||||||
|
const webfinger = await res.json();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(webfinger, {
|
||||||
|
subject: `acct:alice@${host}`,
|
||||||
|
links: [{
|
||||||
|
rel: 'self',
|
||||||
|
type: 'application/activity+json',
|
||||||
|
href: `${origin}/users/${alice.id}`,
|
||||||
|
}, {
|
||||||
|
rel: 'http://webfinger.net/rel/profile-page',
|
||||||
|
type: 'text/html',
|
||||||
|
href: `${origin}/@alice`,
|
||||||
|
}, {
|
||||||
|
rel: 'http://ostatus.org/schema/1.0/subscribe',
|
||||||
|
template: `${origin}/authorize-follow?acct={uri}`,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('host-meta', async () => {
|
||||||
|
const res = await relativeFetch('.well-known/host-meta');
|
||||||
|
assert.ok(res.ok);
|
||||||
|
assert.strictEqual(res.headers.get('Access-Control-Allow-Origin'), '*');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('host-meta.json', async () => {
|
||||||
|
const res = await relativeFetch('.well-known/host-meta.json');
|
||||||
|
assert.ok(res.ok);
|
||||||
|
assert.strictEqual(res.headers.get('Access-Control-Allow-Origin'), '*');
|
||||||
|
|
||||||
|
const hostMeta = await res.json();
|
||||||
|
assert.deepStrictEqual(hostMeta, {
|
||||||
|
links: [{
|
||||||
|
rel: 'lrdd',
|
||||||
|
type: 'application/jrd+json',
|
||||||
|
template: `${origin}/.well-known/webfinger?resource={uri}`,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('oauth-authorization-server', async () => {
|
||||||
|
const res = await relativeFetch('.well-known/oauth-authorization-server');
|
||||||
|
assert.ok(res.ok);
|
||||||
|
assert.strictEqual(res.headers.get('Access-Control-Allow-Origin'), '*');
|
||||||
|
|
||||||
|
const serverInfo = await res.json() as any;
|
||||||
|
assert.strictEqual(serverInfo.issuer, origin);
|
||||||
|
assert.strictEqual(serverInfo.authorization_endpoint, `${origin}/oauth/authorize`);
|
||||||
|
assert.strictEqual(serverInfo.token_endpoint, `${origin}/oauth/token`);
|
||||||
|
});
|
||||||
|
});
|
|
@ -26,6 +26,8 @@ interface UserToken {
|
||||||
|
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
export const port = config.port;
|
export const port = config.port;
|
||||||
|
export const origin = config.url;
|
||||||
|
export const host = new URL(config.url).host;
|
||||||
|
|
||||||
export const cookie = (me: UserToken): string => {
|
export const cookie = (me: UserToken): string => {
|
||||||
return `token=${me.token};`;
|
return `token=${me.token};`;
|
||||||
|
|
|
@ -84,8 +84,8 @@ importers:
|
||||||
specifier: 9.2.0
|
specifier: 9.2.0
|
||||||
version: 9.2.0
|
version: 9.2.0
|
||||||
'@fastify/cors':
|
'@fastify/cors':
|
||||||
specifier: 8.4.2
|
specifier: 8.5.0
|
||||||
version: 8.4.2
|
version: 8.5.0
|
||||||
'@fastify/express':
|
'@fastify/express':
|
||||||
specifier: 2.3.0
|
specifier: 2.3.0
|
||||||
version: 2.3.0
|
version: 2.3.0
|
||||||
|
@ -4303,11 +4303,11 @@ packages:
|
||||||
fastify-plugin: 4.5.0
|
fastify-plugin: 4.5.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@fastify/cors@8.4.2:
|
/@fastify/cors@8.5.0:
|
||||||
resolution: {integrity: sha512-IVynbcPG9eWiJ0P/A1B+KynmiU/yTYbu3ooBUSIeHfca/N1XLb9nIJVCws+YTr2q63MA8Y6QLeXQczEv4npM9g==}
|
resolution: {integrity: sha512-/oZ1QSb02XjP0IK1U0IXktEsw/dUBTxJOW7IpIeO8c/tNalw/KjoNSJv1Sf6eqoBPO+TDGkifq6ynFK3v68HFQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
fastify-plugin: 4.5.0
|
fastify-plugin: 4.5.0
|
||||||
mnemonist: 0.39.5
|
mnemonist: 0.39.6
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@fastify/deepmerge@1.3.0:
|
/@fastify/deepmerge@1.3.0:
|
||||||
|
@ -7281,7 +7281,7 @@ packages:
|
||||||
ts-dedent: 2.2.0
|
ts-dedent: 2.2.0
|
||||||
type-fest: 2.19.0
|
type-fest: 2.19.0
|
||||||
vue: 3.3.12(typescript@5.3.3)
|
vue: 3.3.12(typescript@5.3.3)
|
||||||
vue-component-type-helpers: 1.8.25
|
vue-component-type-helpers: 1.8.27
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- encoding
|
- encoding
|
||||||
- supports-color
|
- supports-color
|
||||||
|
@ -15209,8 +15209,8 @@ packages:
|
||||||
ufo: 1.1.2
|
ufo: 1.1.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/mnemonist@0.39.5:
|
/mnemonist@0.39.6:
|
||||||
resolution: {integrity: sha512-FPUtkhtJ0efmEFGpU14x7jGbTB+s18LrzRL2KgoWz9YvcY3cPomz8tih01GbHwnGk/OmkOKfqd/RAQoc8Lm7DQ==}
|
resolution: {integrity: sha512-A/0v5Z59y63US00cRSLiloEIw3t5G+MiKz4BhX21FI+YBJXBOGW0ohFxTxO08dsOYlzxo87T7vGfZKYp2bcAWA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
obliterator: 2.0.4
|
obliterator: 2.0.4
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -19087,10 +19087,6 @@ packages:
|
||||||
/tweetnacl@0.14.5:
|
/tweetnacl@0.14.5:
|
||||||
resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==}
|
resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==}
|
||||||
|
|
||||||
/twemoji-parser@14.0.0:
|
|
||||||
resolution: {integrity: sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/type-check@0.4.0:
|
/type-check@0.4.0:
|
||||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
@ -19755,8 +19751,8 @@ packages:
|
||||||
resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==}
|
resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/vue-component-type-helpers@1.8.25:
|
/vue-component-type-helpers@1.8.27:
|
||||||
resolution: {integrity: sha512-NCA6sekiJIMnMs4DdORxATXD+/NRkQpS32UC+I1KQJUasx+Z7MZUb3Y+MsKsFmX+PgyTYSteb73JW77AibaCCw==}
|
resolution: {integrity: sha512-0vOfAtI67UjeO1G6UiX5Kd76CqaQ67wrRZiOe7UAb9Jm6GzlUr/fC7CV90XfwapJRjpCMaZFhv1V0ajWRmE9Dg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/vue-component-type-helpers@1.8.4:
|
/vue-component-type-helpers@1.8.4:
|
||||||
|
|
Loading…
Reference in New Issue