mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
feat: init auth service (#2180)
This commit is contained in:
@@ -21,6 +21,7 @@
|
|||||||
"@nestjs/graphql": "^11.0.5",
|
"@nestjs/graphql": "^11.0.5",
|
||||||
"@nestjs/platform-express": "^9.4.0",
|
"@nestjs/platform-express": "^9.4.0",
|
||||||
"@prisma/client": "^4.13.0",
|
"@prisma/client": "^4.13.0",
|
||||||
|
"bcrypt": "^5.1.0",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"graphql": "^16.6.0",
|
"graphql": "^16.6.0",
|
||||||
@@ -33,6 +34,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/testing": "^9.4.0",
|
"@nestjs/testing": "^9.4.0",
|
||||||
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"@types/jsonwebtoken": "^9.0.1",
|
"@types/jsonwebtoken": "^9.0.1",
|
||||||
"@types/lodash-es": "^4.17.7",
|
"@types/lodash-es": "^4.17.7",
|
||||||
|
|||||||
19
apps/server/scripts/gen-auth-key.ts
Normal file
19
apps/server/scripts/gen-auth-key.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import crypto from 'node:crypto';
|
||||||
|
|
||||||
|
import { genSalt } from 'bcrypt';
|
||||||
|
|
||||||
|
const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', {
|
||||||
|
namedCurve: 'prime256v1',
|
||||||
|
publicKeyEncoding: {
|
||||||
|
type: 'spki',
|
||||||
|
format: 'pem',
|
||||||
|
},
|
||||||
|
privateKeyEncoding: {
|
||||||
|
type: 'pkcs8',
|
||||||
|
format: 'pem',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Salt:\n', await genSalt(10));
|
||||||
|
console.log('ECDSA Public Key:\n', publicKey);
|
||||||
|
console.log('ECDSA Private Key:\n', privateKey);
|
||||||
@@ -12,7 +12,7 @@ const root = fileURLToPath(new URL('..', import.meta.url));
|
|||||||
const testDir = resolve(root, 'src', 'tests');
|
const testDir = resolve(root, 'src', 'tests');
|
||||||
const files = await readdir(testDir);
|
const files = await readdir(testDir);
|
||||||
|
|
||||||
const args = [...pkg.nodemonConfig.nodeArgs, '--test'];
|
const sharedArgs = [...pkg.nodemonConfig.nodeArgs, '--test'];
|
||||||
|
|
||||||
const env = {
|
const env = {
|
||||||
PATH: process.env.PATH,
|
PATH: process.env.PATH,
|
||||||
@@ -21,7 +21,7 @@ const env = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (process.argv[2] === 'all') {
|
if (process.argv[2] === 'all') {
|
||||||
const cp = spawn('node', [...args, resolve(testDir, '*')], {
|
const cp = spawn('node', [...sharedArgs, resolve(testDir, '*')], {
|
||||||
cwd: root,
|
cwd: root,
|
||||||
env,
|
env,
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
@@ -44,12 +44,21 @@ if (process.argv[2] === 'all') {
|
|||||||
|
|
||||||
const target = resolve(testDir, result.file);
|
const target = resolve(testDir, result.file);
|
||||||
|
|
||||||
const cp = spawn('node', [...args, target], {
|
const cp = spawn(
|
||||||
cwd: root,
|
'node',
|
||||||
env,
|
[
|
||||||
stdio: 'inherit',
|
...sharedArgs,
|
||||||
shell: true,
|
'--test-reporter=spec',
|
||||||
});
|
'--test-reporter-destination=stdout',
|
||||||
|
target,
|
||||||
|
],
|
||||||
|
{
|
||||||
|
cwd: root,
|
||||||
|
env,
|
||||||
|
stdio: 'inherit',
|
||||||
|
shell: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
cp.on('exit', code => {
|
cp.on('exit', code => {
|
||||||
process.exit(code ?? 0);
|
process.exit(code ?? 0);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -70,9 +70,9 @@ export function parseEnvValue(value: string | undefined, type?: EnvConfigType) {
|
|||||||
export interface AFFiNEConfig {
|
export interface AFFiNEConfig {
|
||||||
ENV_MAP: Record<string, ConfigPaths | [ConfigPaths, EnvConfigType?]>;
|
ENV_MAP: Record<string, ConfigPaths | [ConfigPaths, EnvConfigType?]>;
|
||||||
/**
|
/**
|
||||||
* Application sign key secret
|
* Server Identity
|
||||||
*/
|
*/
|
||||||
readonly secret: string;
|
readonly serverId: string;
|
||||||
/**
|
/**
|
||||||
* System version
|
* System version
|
||||||
*/
|
*/
|
||||||
@@ -169,6 +169,28 @@ export interface AFFiNEConfig {
|
|||||||
* authentication config
|
* authentication config
|
||||||
*/
|
*/
|
||||||
auth: {
|
auth: {
|
||||||
|
/**
|
||||||
|
* Application sign key secret
|
||||||
|
*/
|
||||||
|
readonly salt: string;
|
||||||
|
/**
|
||||||
|
* Application access token expiration time
|
||||||
|
*/
|
||||||
|
readonly accessTokenExpiresIn: string;
|
||||||
|
/**
|
||||||
|
* Application refresh token expiration time
|
||||||
|
*/
|
||||||
|
readonly refreshTokenExpiresIn: string;
|
||||||
|
/**
|
||||||
|
* Application public key
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
readonly publicKey: string;
|
||||||
|
/**
|
||||||
|
* Application private key
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
readonly privateKey: string;
|
||||||
/**
|
/**
|
||||||
* whether allow user to signup with email directly
|
* whether allow user to signup with email directly
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,8 +1,23 @@
|
|||||||
|
/// <reference types="../global.d.ts" />
|
||||||
|
|
||||||
import pkg from '../../package.json' assert { type: 'json' };
|
import pkg from '../../package.json' assert { type: 'json' };
|
||||||
import type { AFFiNEConfig } from './def';
|
import type { AFFiNEConfig } from './def';
|
||||||
|
|
||||||
|
// Don't use this in production
|
||||||
|
export const examplePublicKey = `-----BEGIN PUBLIC KEY-----
|
||||||
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEnxM+GhB6eNKPmTP6uH5Gpr+bmQ87
|
||||||
|
hHGeOiCsay0w/aPwMqzAOKkZGqX+HZ9BNGy/yiXmnscey5b2vOTzxtRvxA==
|
||||||
|
-----END PUBLIC KEY-----`;
|
||||||
|
|
||||||
|
// Don't use this in production
|
||||||
|
export const examplePrivateKey = `-----BEGIN PRIVATE KEY-----
|
||||||
|
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWOog5SFXs1Vjh/WP
|
||||||
|
QCYPQKgf/jsNmWsvD+jYSn6mi3yhRANCAASfEz4aEHp40o+ZM/q4fkamv5uZDzuE
|
||||||
|
cZ46IKxrLTD9o/AyrMA4qRkapf4dn0E0bL/KJeaexx7Llva85PPG1G/E
|
||||||
|
-----END PRIVATE KEY-----`;
|
||||||
|
|
||||||
export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => ({
|
export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => ({
|
||||||
secret: 'secret',
|
serverId: 'affine-nestjs-server',
|
||||||
version: pkg.version,
|
version: pkg.version,
|
||||||
ENV_MAP: {},
|
ENV_MAP: {},
|
||||||
env: process.env.NODE_ENV ?? 'development',
|
env: process.env.NODE_ENV ?? 'development',
|
||||||
@@ -41,6 +56,11 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => ({
|
|||||||
debug: true,
|
debug: true,
|
||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
|
salt: '$2b$10$x4VDo2nmlo74yB5jflNhlu',
|
||||||
|
accessTokenExpiresIn: '1h',
|
||||||
|
refreshTokenExpiresIn: '7d',
|
||||||
|
publicKey: examplePublicKey,
|
||||||
|
privateKey: examplePrivateKey,
|
||||||
enableSignup: true,
|
enableSignup: true,
|
||||||
enableOauth: false,
|
enableOauth: false,
|
||||||
oauthProviders: {},
|
oauthProviders: {},
|
||||||
|
|||||||
@@ -17,4 +17,9 @@ const app = await NestFactory.create<NestExpressApplication>(AppModule, {
|
|||||||
bodyParser: true,
|
bodyParser: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
await app.listen(process.env.PORT ?? 3010);
|
const host = process.env.HOST ?? 'localhost';
|
||||||
|
const port = process.env.PORT ?? 3010;
|
||||||
|
|
||||||
|
await app.listen(port, host);
|
||||||
|
|
||||||
|
console.log(`Listening on http://${host}:${port}`);
|
||||||
|
|||||||
@@ -36,8 +36,7 @@ export class AuthResolver {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
token: this.auth.sign(user),
|
token: this.auth.sign(user),
|
||||||
// TODO: impl
|
refresh: this.auth.refresh(user),
|
||||||
refresh: '',
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
UnauthorizedException,
|
UnauthorizedException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { User } from '@prisma/client';
|
import { User } from '@prisma/client';
|
||||||
|
import { compare, hash } from 'bcrypt';
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
|
|
||||||
import { Config } from '../../config';
|
import { Config } from '../../config';
|
||||||
@@ -16,30 +17,76 @@ export class AuthService {
|
|||||||
constructor(private config: Config, private prisma: PrismaService) {}
|
constructor(private config: Config, private prisma: PrismaService) {}
|
||||||
|
|
||||||
sign(user: UserClaim) {
|
sign(user: UserClaim) {
|
||||||
return jwt.sign(user, this.config.secret);
|
return jwt.sign(user, this.config.auth.privateKey, {
|
||||||
|
algorithm: 'ES256',
|
||||||
|
subject: user.id,
|
||||||
|
issuer: this.config.serverId,
|
||||||
|
expiresIn: this.config.auth.accessTokenExpiresIn,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh(user: UserClaim) {
|
||||||
|
return jwt.sign(user, this.config.auth.privateKey, {
|
||||||
|
algorithm: 'ES256',
|
||||||
|
subject: user.id,
|
||||||
|
issuer: this.config.serverId,
|
||||||
|
expiresIn: this.config.auth.refreshTokenExpiresIn,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
verify(token: string) {
|
verify(token: string) {
|
||||||
try {
|
try {
|
||||||
const claims = jwt.verify(token, this.config.secret) as UserClaim;
|
return jwt.verify(token, this.config.auth.publicKey, {
|
||||||
return claims;
|
algorithms: ['ES256'],
|
||||||
|
}) as UserClaim;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new UnauthorizedException('Invalid token');
|
throw new UnauthorizedException('Invalid token');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async signIn(email: string, password: string) {
|
async signIn(email: string, password: string): Promise<User> {
|
||||||
const user = await this.prisma.user.findFirst({
|
const user = await this.prisma.user.findFirst({
|
||||||
where: {
|
where: {
|
||||||
email,
|
email,
|
||||||
password,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new BadRequestException('Invalid email or password');
|
throw new BadRequestException('Invalid email');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user.password) {
|
||||||
|
throw new BadRequestException('User has no password');
|
||||||
|
}
|
||||||
|
|
||||||
|
const equal = await compare(password, user.password);
|
||||||
|
|
||||||
|
if (!equal) {
|
||||||
|
throw new UnauthorizedException('Invalid password');
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async register(name: string, email: string, password: string): Promise<User> {
|
||||||
|
const hashedPassword = await hash(password, this.config.auth.salt);
|
||||||
|
|
||||||
|
const user = await this.prisma.user.findFirst({
|
||||||
|
where: {
|
||||||
|
email,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
throw new BadRequestException('Email already exists');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.prisma.user.create({
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
password: hashedPassword,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { afterEach, beforeEach, describe, test } from 'node:test';
|
|||||||
|
|
||||||
import { INestApplication } from '@nestjs/common';
|
import { INestApplication } from '@nestjs/common';
|
||||||
import { Test } from '@nestjs/testing';
|
import { Test } from '@nestjs/testing';
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
import { hash } from 'bcrypt';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
|
|
||||||
import { AppModule } from '../app';
|
import { AppModule } from '../app';
|
||||||
@@ -12,10 +14,24 @@ const gql = '/graphql';
|
|||||||
|
|
||||||
globalThis.AFFiNE = getDefaultAFFiNEConfig();
|
globalThis.AFFiNE = getDefaultAFFiNEConfig();
|
||||||
|
|
||||||
// please run `ts-node-esm ./scripts/init-db.ts` before running this test
|
|
||||||
describe('AppModule', () => {
|
describe('AppModule', () => {
|
||||||
let app: INestApplication;
|
let app: INestApplication;
|
||||||
|
|
||||||
|
// cleanup database before each test
|
||||||
|
beforeEach(async () => {
|
||||||
|
const client = new PrismaClient();
|
||||||
|
await client.$connect();
|
||||||
|
await client.user.deleteMany({});
|
||||||
|
await client.user.create({
|
||||||
|
data: {
|
||||||
|
id: '1',
|
||||||
|
name: 'Alex Yang',
|
||||||
|
email: 'alex.yang@example.org',
|
||||||
|
password: await hash('123456', globalThis.AFFiNE.auth.salt),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module = await Test.createTestingModule({
|
const module = await Test.createTestingModule({
|
||||||
imports: [AppModule],
|
imports: [AppModule],
|
||||||
|
|||||||
82
apps/server/src/tests/auth.spec.ts
Normal file
82
apps/server/src/tests/auth.spec.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { ok, throws } from 'node:assert';
|
||||||
|
import { beforeEach, test } from 'node:test';
|
||||||
|
|
||||||
|
import { UnauthorizedException } from '@nestjs/common';
|
||||||
|
import { Test } from '@nestjs/testing';
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
import { Config, ConfigModule } from '../config';
|
||||||
|
import { getDefaultAFFiNEConfig } from '../config/default';
|
||||||
|
import { GqlModule } from '../graphql.module';
|
||||||
|
import { AuthModule } from '../modules/auth';
|
||||||
|
import { AuthService } from '../modules/auth/service';
|
||||||
|
import { PrismaModule } from '../prisma';
|
||||||
|
|
||||||
|
globalThis.AFFiNE = getDefaultAFFiNEConfig();
|
||||||
|
|
||||||
|
let auth: AuthService;
|
||||||
|
let config: Config;
|
||||||
|
|
||||||
|
// cleanup database before each test
|
||||||
|
beforeEach(async () => {
|
||||||
|
const client = new PrismaClient();
|
||||||
|
await client.$connect();
|
||||||
|
await client.user.deleteMany({});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module = await Test.createTestingModule({
|
||||||
|
imports: [
|
||||||
|
ConfigModule.forRoot({
|
||||||
|
auth: {
|
||||||
|
accessTokenExpiresIn: '1s',
|
||||||
|
refreshTokenExpiresIn: '3s',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
PrismaModule,
|
||||||
|
GqlModule,
|
||||||
|
AuthModule,
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
config = module.get(Config);
|
||||||
|
auth = module.get(AuthService);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function sleep(ms: number) {
|
||||||
|
return new Promise<void>(resolve => {
|
||||||
|
setTimeout(resolve, ms);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('should be able to register and signIn', async () => {
|
||||||
|
await auth.register('Alex Yang', 'alexyang@example.org', '123456');
|
||||||
|
await auth.signIn('alexyang@example.org', '123456');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to verify', async () => {
|
||||||
|
await auth.register('Alex Yang', 'alexyang@example.org', '123456');
|
||||||
|
await auth.signIn('alexyang@example.org', '123456');
|
||||||
|
const user = {
|
||||||
|
id: '1',
|
||||||
|
name: 'Alex Yang',
|
||||||
|
email: 'alexyang@example.org',
|
||||||
|
};
|
||||||
|
{
|
||||||
|
const token = auth.sign(user);
|
||||||
|
const clain = auth.verify(token);
|
||||||
|
ok(clain.id === '1');
|
||||||
|
ok(clain.name === 'Alex Yang');
|
||||||
|
ok(clain.email === 'alexyang@example.org');
|
||||||
|
await sleep(1050);
|
||||||
|
throws(() => auth.verify(token), UnauthorizedException, 'Invalid token');
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const token = auth.refresh(user);
|
||||||
|
const clain = auth.verify(token);
|
||||||
|
ok(clain.id === '1');
|
||||||
|
ok(clain.name === 'Alex Yang');
|
||||||
|
ok(clain.email === 'alexyang@example.org');
|
||||||
|
await sleep(3050);
|
||||||
|
throws(() => auth.verify(token), UnauthorizedException, 'Invalid token');
|
||||||
|
}
|
||||||
|
});
|
||||||
62
yarn.lock
62
yarn.lock
@@ -248,11 +248,13 @@ __metadata:
|
|||||||
"@nestjs/platform-express": ^9.4.0
|
"@nestjs/platform-express": ^9.4.0
|
||||||
"@nestjs/testing": ^9.4.0
|
"@nestjs/testing": ^9.4.0
|
||||||
"@prisma/client": ^4.13.0
|
"@prisma/client": ^4.13.0
|
||||||
|
"@types/bcrypt": ^5.0.0
|
||||||
"@types/express": ^4.17.17
|
"@types/express": ^4.17.17
|
||||||
"@types/jsonwebtoken": ^9.0.1
|
"@types/jsonwebtoken": ^9.0.1
|
||||||
"@types/lodash-es": ^4.17.7
|
"@types/lodash-es": ^4.17.7
|
||||||
"@types/node": ^18.16.2
|
"@types/node": ^18.16.2
|
||||||
"@types/supertest": ^2.0.12
|
"@types/supertest": ^2.0.12
|
||||||
|
bcrypt: ^5.1.0
|
||||||
c8: ^7.13.0
|
c8: ^7.13.0
|
||||||
dotenv: ^16.0.3
|
dotenv: ^16.0.3
|
||||||
express: ^4.18.2
|
express: ^4.18.2
|
||||||
@@ -5162,6 +5164,25 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@mapbox/node-pre-gyp@npm:^1.0.10":
|
||||||
|
version: 1.0.10
|
||||||
|
resolution: "@mapbox/node-pre-gyp@npm:1.0.10"
|
||||||
|
dependencies:
|
||||||
|
detect-libc: ^2.0.0
|
||||||
|
https-proxy-agent: ^5.0.0
|
||||||
|
make-dir: ^3.1.0
|
||||||
|
node-fetch: ^2.6.7
|
||||||
|
nopt: ^5.0.0
|
||||||
|
npmlog: ^5.0.1
|
||||||
|
rimraf: ^3.0.2
|
||||||
|
semver: ^7.3.5
|
||||||
|
tar: ^6.1.11
|
||||||
|
bin:
|
||||||
|
node-pre-gyp: bin/node-pre-gyp
|
||||||
|
checksum: 1a98db05d955b74dad3814679593df293b9194853698f3f5f1ed00ecd93128cdd4b14fb8767fe44ac6981ef05c23effcfdc88710e7c1de99ccb6f647890597c8
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@mdx-js/react@npm:^2.1.5":
|
"@mdx-js/react@npm:^2.1.5":
|
||||||
version: 2.3.0
|
version: 2.3.0
|
||||||
resolution: "@mdx-js/react@npm:2.3.0"
|
resolution: "@mdx-js/react@npm:2.3.0"
|
||||||
@@ -8120,6 +8141,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@types/bcrypt@npm:^5.0.0":
|
||||||
|
version: 5.0.0
|
||||||
|
resolution: "@types/bcrypt@npm:5.0.0"
|
||||||
|
dependencies:
|
||||||
|
"@types/node": "*"
|
||||||
|
checksum: 063c32c7a519d64768dfc0169a319b8244d6a6cb50a355c93992b3c5fee1dbc236526a1111f0e7bb25abc8b0473e5f40a5edfeb8b33cad2a6ea35aa2d7d7db14
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@types/better-sqlite3@npm:^7.6.4":
|
"@types/better-sqlite3@npm:^7.6.4":
|
||||||
version: 7.6.4
|
version: 7.6.4
|
||||||
resolution: "@types/better-sqlite3@npm:7.6.4"
|
resolution: "@types/better-sqlite3@npm:7.6.4"
|
||||||
@@ -10376,6 +10406,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"bcrypt@npm:^5.1.0":
|
||||||
|
version: 5.1.0
|
||||||
|
resolution: "bcrypt@npm:5.1.0"
|
||||||
|
dependencies:
|
||||||
|
"@mapbox/node-pre-gyp": ^1.0.10
|
||||||
|
node-addon-api: ^5.0.0
|
||||||
|
checksum: a590b65d276d75d861dc85acc3128508b8f78c87431719658ea3be7996368b34b397b6efefe6bca0a3d555bf41a9267307fd4ce04e956598fca3ba81199c6706
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"better-opn@npm:^2.1.1":
|
"better-opn@npm:^2.1.1":
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
resolution: "better-opn@npm:2.1.1"
|
resolution: "better-opn@npm:2.1.1"
|
||||||
@@ -18332,7 +18372,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"make-dir@npm:^3.0.0, make-dir@npm:^3.0.2":
|
"make-dir@npm:^3.0.0, make-dir@npm:^3.0.2, make-dir@npm:^3.1.0":
|
||||||
version: 3.1.0
|
version: 3.1.0
|
||||||
resolution: "make-dir@npm:3.1.0"
|
resolution: "make-dir@npm:3.1.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -19191,6 +19231,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"node-addon-api@npm:^5.0.0":
|
||||||
|
version: 5.1.0
|
||||||
|
resolution: "node-addon-api@npm:5.1.0"
|
||||||
|
dependencies:
|
||||||
|
node-gyp: latest
|
||||||
|
checksum: 2508bd2d2981945406243a7bd31362fc7af8b70b8b4d65f869c61731800058fb818cc2fd36c8eac714ddd0e568cc85becf5e165cebbdf7b5024d5151bbc75ea1
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"node-api-version@npm:^0.1.4":
|
"node-api-version@npm:^0.1.4":
|
||||||
version: 0.1.4
|
version: 0.1.4
|
||||||
resolution: "node-api-version@npm:0.1.4"
|
resolution: "node-api-version@npm:0.1.4"
|
||||||
@@ -19336,6 +19385,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"nopt@npm:^5.0.0":
|
||||||
|
version: 5.0.0
|
||||||
|
resolution: "nopt@npm:5.0.0"
|
||||||
|
dependencies:
|
||||||
|
abbrev: 1
|
||||||
|
bin:
|
||||||
|
nopt: bin/nopt.js
|
||||||
|
checksum: d35fdec187269503843924e0114c0c6533fb54bbf1620d0f28b4b60ba01712d6687f62565c55cc20a504eff0fbe5c63e22340c3fad549ad40469ffb611b04f2f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"nopt@npm:^6.0.0":
|
"nopt@npm:^6.0.0":
|
||||||
version: 6.0.0
|
version: 6.0.0
|
||||||
resolution: "nopt@npm:6.0.0"
|
resolution: "nopt@npm:6.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user