feat: implement oauth2
This commit is contained in:
parent
76093fb5e8
commit
dd486229c4
13
.env.example
13
.env.example
@ -6,6 +6,13 @@ JWT_EXPIRES_IN=1h
|
|||||||
REFRESH_JWT_SECRET=
|
REFRESH_JWT_SECRET=
|
||||||
REFRESH_JWT_EXPIRES_IN=7d
|
REFRESH_JWT_EXPIRES_IN=7d
|
||||||
|
|
||||||
DISCORD_CLIENT_ID=
|
OAUTH2_CLIENT_ID=
|
||||||
DISCORD_CLIENT_SECRET=
|
OAUTH2_CLIENT_SECRET=
|
||||||
DISCORD_CALLBACK_URL=http://localhost:3000/auth/discord/callback
|
OAUTH2_TOKEN_URL=
|
||||||
|
OAUTH2_PROFILE_URL=
|
||||||
|
OAUTH2_AUTHORIZATION_URL=
|
||||||
|
OAUTH2_SCOPES=openid email profile
|
||||||
|
|
||||||
|
OAUTH2_CALLBACK_URL=http://localhost:3000/auth/callback
|
||||||
|
|
||||||
|
PORT=3000
|
@ -37,8 +37,8 @@
|
|||||||
"joi": "^17.13.3",
|
"joi": "^17.13.3",
|
||||||
"nestjs-prisma": "^0.23.0",
|
"nestjs-prisma": "^0.23.0",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
"passport-discord": "^0.1.4",
|
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
|
"passport-oauth2": "^1.8.0",
|
||||||
"prisma": "^6.0.1",
|
"prisma": "^6.0.1",
|
||||||
"reflect-metadata": "^0.2.0",
|
"reflect-metadata": "^0.2.0",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
@ -49,8 +49,8 @@
|
|||||||
"@nestjs/schematics": "^10.0.0",
|
"@nestjs/schematics": "^10.0.0",
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/node": "^20.3.1",
|
"@types/node": "^20.3.1",
|
||||||
"@types/passport-discord": "^0.1.14",
|
|
||||||
"@types/passport-jwt": "^4.0.1",
|
"@types/passport-jwt": "^4.0.1",
|
||||||
|
"@types/passport-oauth2": "^1.4.17",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.0.0",
|
||||||
|
@ -12,6 +12,7 @@ model User {
|
|||||||
username String? @unique
|
username String? @unique
|
||||||
role Role @default(STUDENT)
|
role Role @default(STUDENT)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
providerId String @unique
|
||||||
|
|
||||||
Class Class[]
|
Class Class[]
|
||||||
SentMessages UserMessage[] @relation("SentMessages")
|
SentMessages UserMessage[] @relation("SentMessages")
|
||||||
|
@ -27,7 +27,7 @@ import { AppController } from "./app.controller";
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
PrismaModule.forRoot({
|
PrismaModule.forRoot({
|
||||||
isGlobal: true
|
isGlobal: true,
|
||||||
}),
|
}),
|
||||||
UserModule,
|
UserModule,
|
||||||
AuthModule,
|
AuthModule,
|
||||||
|
@ -8,10 +8,12 @@ export default () => ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
oauth2: {
|
oauth2: {
|
||||||
discord: {
|
authorizationURL: process.env.OAUTH2_AUTHORIZATION_URL,
|
||||||
clientId: process.env.DISCORD_CLIENT_ID,
|
tokenURL: process.env.OAUTH2_TOKEN_URL,
|
||||||
clientSecret: process.env.DISCORD_CLIENT_SECRET,
|
clientID: process.env.OAUTH2_CLIENT_ID,
|
||||||
callbackUrl: process.env.DISCORD_CALLBACK_URL,
|
clientSecret: process.env.OAUTH2_CLIENT_SECRET,
|
||||||
},
|
callbackURL: process.env.OAUTH2_CALLBACK_URL,
|
||||||
|
profileURL: process.env.OAUTH2_PROFILE_URL,
|
||||||
|
scopes: process.env.OAUTH2_SCOPES,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
6
src/interfaces/oauth2.ts
Normal file
6
src/interfaces/oauth2.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export interface Oauth2Profile {
|
||||||
|
id?: string;
|
||||||
|
sub?: string;
|
||||||
|
|
||||||
|
email: string;
|
||||||
|
}
|
@ -1,49 +1,47 @@
|
|||||||
import { Controller, Get, Req, Res, UseGuards } from "@nestjs/common";
|
import { Controller, Get, Req, Res, UseGuards } from "@nestjs/common";
|
||||||
import { ConfigService } from "@nestjs/config";
|
|
||||||
import { ApiBearerAuth, ApiOkResponse } from "@nestjs/swagger";
|
import { ApiBearerAuth, ApiOkResponse } from "@nestjs/swagger";
|
||||||
|
|
||||||
import { URLSearchParams } from "node:url";
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
|
||||||
import { AuthService } from "./auth.service";
|
import { AuthService } from "./auth.service";
|
||||||
import { DiscordAuthGuard } from "./guards/discord.guard";
|
import { Oauth2AuthGuard } from "./guards/oauth2.guard";
|
||||||
import { RefreshJwtAuthGuard } from "./guards/refresh.guard";
|
import { RefreshJwtAuthGuard } from "./guards/refresh.guard";
|
||||||
|
|
||||||
@Controller("auth")
|
@Controller("auth")
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly configService: ConfigService,
|
|
||||||
private readonly authService: AuthService,
|
private readonly authService: AuthService,
|
||||||
|
private readonly configService: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get("providers")
|
@Get("login")
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({
|
||||||
description: "List of OAuth2 providers",
|
description: "Redirect to login page",
|
||||||
example: {
|
example: {
|
||||||
discord: { url: "https://discord.com/oauth2/authorize?..." },
|
url: "http://localhost:3000/auth/login",
|
||||||
keycloak: { url: "https://keycloak.com/oauth2/authorize?..." },
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
getProviders() {
|
async login() {
|
||||||
const discordOauth2Params = new URLSearchParams({
|
const authorizationURL = this.configService.get(
|
||||||
client_id: this.configService.get<string>(
|
"oauth2.authorizationURL",
|
||||||
"oauth2.discord.clientId",
|
);
|
||||||
),
|
const clientID = this.configService.get("oauth2.clientID");
|
||||||
|
const callbackURL = this.configService.get("oauth2.callbackURL");
|
||||||
|
const scopes = this.configService.get("oauth2.scopes");
|
||||||
|
|
||||||
|
const searchParams = new URLSearchParams({
|
||||||
response_type: "code",
|
response_type: "code",
|
||||||
redirect_uri: this.configService.get<string>(
|
client_id: clientID,
|
||||||
"oauth2.discord.callbackUrl",
|
redirect_uri: callbackURL,
|
||||||
),
|
scope: scopes,
|
||||||
scope: "identify email",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
discord: {
|
url: `${authorizationURL}?${searchParams.toString()}`,
|
||||||
url: `https://discord.com/oauth2/authorize?${discordOauth2Params.toString()}`,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("discord/callback")
|
@Get("callback")
|
||||||
@UseGuards(DiscordAuthGuard)
|
@UseGuards(Oauth2AuthGuard)
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({
|
||||||
description: "Return JWT token",
|
description: "Return JWT token",
|
||||||
example: {
|
example: {
|
||||||
@ -51,7 +49,7 @@ export class AuthController {
|
|||||||
refreshtoken: "Hello World!",
|
refreshtoken: "Hello World!",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
callbackDiscord(@Req() req, @Res() res) {
|
callback(@Req() req, @Res() res) {
|
||||||
const { user } = req;
|
const { user } = req;
|
||||||
|
|
||||||
res.send(user);
|
res.send(user);
|
||||||
|
@ -4,13 +4,13 @@ import { AuthService } from "./auth.service";
|
|||||||
|
|
||||||
import { UserModule } from "@Modules/user/user.module";
|
import { UserModule } from "@Modules/user/user.module";
|
||||||
|
|
||||||
import { DiscordStrategy } from "./strategy/discord.strategy";
|
import { Oauth2Strategy } from "./strategy/oauth2strategy";
|
||||||
import { JWTStrategy } from "./strategy/jwt.strategy";
|
import { JWTStrategy } from "./strategy/jwt.strategy";
|
||||||
import { RefreshJWTStrategy } from "./strategy/refresh.strategy";
|
import { RefreshJWTStrategy } from "./strategy/refresh.strategy";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [UserModule],
|
imports: [UserModule],
|
||||||
controllers: [AuthController],
|
controllers: [AuthController],
|
||||||
providers: [AuthService, DiscordStrategy, JWTStrategy, RefreshJWTStrategy],
|
providers: [AuthService, Oauth2Strategy, JWTStrategy, RefreshJWTStrategy],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule {}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
import { Injectable, UnauthorizedException } from "@nestjs/common";
|
|
||||||
import { AuthGuard } from "@nestjs/passport";
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class DiscordAuthGuard extends AuthGuard("discord") {
|
|
||||||
handleRequest(err, user, info) {
|
|
||||||
if (err || !user) {
|
|
||||||
const errorMessage = info?.message || "Authentication failed";
|
|
||||||
throw new UnauthorizedException(errorMessage);
|
|
||||||
}
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
}
|
|
5
src/modules/auth/guards/oauth2.guard.ts
Normal file
5
src/modules/auth/guards/oauth2.guard.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { AuthGuard } from "@nestjs/passport";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class Oauth2AuthGuard extends AuthGuard("oauth2") {}
|
@ -1,40 +0,0 @@
|
|||||||
import { Injectable } from "@nestjs/common";
|
|
||||||
import { ConfigService } from "@nestjs/config";
|
|
||||||
import { PassportStrategy } from "@nestjs/passport";
|
|
||||||
import { Profile, Strategy } from "passport-discord";
|
|
||||||
import { AuthService } from "../auth.service";
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class DiscordStrategy extends PassportStrategy(Strategy, "discord") {
|
|
||||||
configService: ConfigService;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly authService: AuthService,
|
|
||||||
configService: ConfigService,
|
|
||||||
) {
|
|
||||||
super({
|
|
||||||
clientID: configService.get<string>("oauth2.discord.clientId"),
|
|
||||||
clientSecret: configService.get<string>(
|
|
||||||
"oauth2.discord.clientSecret",
|
|
||||||
),
|
|
||||||
callbackURL: configService.get<string>(
|
|
||||||
"oauth2.discord.callbackUrl",
|
|
||||||
),
|
|
||||||
scope: ["identify", "email"],
|
|
||||||
});
|
|
||||||
|
|
||||||
this.configService = configService;
|
|
||||||
}
|
|
||||||
|
|
||||||
async validate(
|
|
||||||
_accessToken: string,
|
|
||||||
_refreshToken: string,
|
|
||||||
profile: Profile,
|
|
||||||
done: Function,
|
|
||||||
) {
|
|
||||||
const accessToken = this.authService.accessToken({ id: profile.id });
|
|
||||||
const refreshToken = this.authService.refreshToken({ id: profile.id });
|
|
||||||
|
|
||||||
done(null, { accessToken, refreshToken });
|
|
||||||
}
|
|
||||||
}
|
|
79
src/modules/auth/strategy/oauth2strategy.ts
Normal file
79
src/modules/auth/strategy/oauth2strategy.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { Oauth2Profile } from "@/interfaces/oauth2";
|
||||||
|
import { UserService } from "@/modules/user/user.service";
|
||||||
|
import { Injectable, UnauthorizedException } from "@nestjs/common";
|
||||||
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
import { PassportStrategy } from "@nestjs/passport";
|
||||||
|
import axios from "axios";
|
||||||
|
import { Strategy } from "passport-oauth2";
|
||||||
|
import { AuthService } from "../auth.service";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class Oauth2Strategy extends PassportStrategy(Strategy, "oauth2") {
|
||||||
|
configService: ConfigService;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
// @ts-ignore
|
||||||
|
private readonly authService: AuthService,
|
||||||
|
private readonly userSerivce: UserService,
|
||||||
|
configService: ConfigService,
|
||||||
|
) {
|
||||||
|
super({
|
||||||
|
authorizationURL: configService.get("oauth2.authorizationURL"),
|
||||||
|
tokenURL: configService.get("oauth2.tokenURL"),
|
||||||
|
clientID: configService.get("oauth2.clientID"),
|
||||||
|
clientSecret: configService.get("oauth2.clientSecret"),
|
||||||
|
callbackURL: configService.get("oauth2.callbackURL"),
|
||||||
|
scope: configService.get("oauth2.scopes"),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.configService = configService;
|
||||||
|
}
|
||||||
|
|
||||||
|
userProfile(
|
||||||
|
accessToken: string,
|
||||||
|
done: (err?: unknown, profile?: Oauth2Profile) => void,
|
||||||
|
): void {
|
||||||
|
try {
|
||||||
|
axios
|
||||||
|
.get(this.configService.get("oauth2.profileURL"), {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
const { data } = response;
|
||||||
|
done(null, data);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
done(error);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
done(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate(
|
||||||
|
_accessToken: string,
|
||||||
|
_refreshToken: string,
|
||||||
|
profile: Oauth2Profile,
|
||||||
|
): Promise<{ accessToken: string; refreshToken: string }> {
|
||||||
|
const user = await this.userSerivce.findOne({
|
||||||
|
providerId: profile.sub ?? profile.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) throw new UnauthorizedException("User not found");
|
||||||
|
|
||||||
|
const accessToken = this.authService.accessToken({
|
||||||
|
id: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const refreshToken = this.authService.refreshToken({
|
||||||
|
id: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken,
|
||||||
|
refreshToken,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
import { ArrayMaxSize, ArrayMinSize, IsArray, IsString } from "class-validator";
|
import { ArrayMaxSize, ArrayMinSize, IsArray, IsString } from "class-validator";
|
||||||
|
|
||||||
export class BulkDeleteUserDTO {
|
export class BulkDeleteUserDTO {
|
||||||
@ -5,5 +6,6 @@ export class BulkDeleteUserDTO {
|
|||||||
@IsString({ each: true })
|
@IsString({ each: true })
|
||||||
@ArrayMinSize(1)
|
@ArrayMinSize(1)
|
||||||
@ArrayMaxSize(10)
|
@ArrayMaxSize(10)
|
||||||
|
@ApiProperty()
|
||||||
ids: string[];
|
ids: string[];
|
||||||
}
|
}
|
||||||
|
@ -5,4 +5,8 @@ export class CreateUserDTO {
|
|||||||
@IsString()
|
@IsString()
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
username: string;
|
username: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@ApiProperty()
|
||||||
|
providerId: string;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||||
import { PrismaService } from "nestjs-prisma";
|
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { PrismaService } from "nestjs-prisma";
|
||||||
|
|
||||||
import { CreateUserDTO } from "./dto/create-user.dto";
|
import { CreateUserDTO } from "./dto/create-user.dto";
|
||||||
import { UpdateUserDTO } from "./dto/update-user.dto";
|
import { UpdateUserDTO } from "./dto/update-user.dto";
|
||||||
@ -31,10 +31,17 @@ export class UserService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findOne(where: Prisma.UserWhereInput) {
|
||||||
|
return await this.prisma.user.findFirst({
|
||||||
|
where,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async create(createUserInput: CreateUserDTO) {
|
async create(createUserInput: CreateUserDTO) {
|
||||||
return await this.prisma.user.create({
|
return await this.prisma.user.create({
|
||||||
data: {
|
data: {
|
||||||
username: createUserInput.username,
|
username: createUserInput.username,
|
||||||
|
providerId: createUserInput.providerId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,11 @@ export const envValidation = Joi.object({
|
|||||||
REFRESH_JWT_SECRET: Joi.string().required(),
|
REFRESH_JWT_SECRET: Joi.string().required(),
|
||||||
REFRESH_JWT_EXPIRES_IN: Joi.string().required(),
|
REFRESH_JWT_EXPIRES_IN: Joi.string().required(),
|
||||||
|
|
||||||
DISCORD_CLIENT_ID: Joi.string().required(),
|
OAUTH2_AUTHORIZATION_URL: Joi.string().uri().required(),
|
||||||
DISCORD_CLIENT_SECRET: Joi.string().required(),
|
OAUTH2_TOKEN_URL: Joi.string().uri().required(),
|
||||||
DISCORD_CALLBACK_URL: Joi.string().required(),
|
OAUTH2_CLIENT_ID: Joi.string().required(),
|
||||||
|
OAUTH2_CLIENT_SECRET: Joi.string().required(),
|
||||||
|
OAUTH2_CALLBACK_URL: Joi.string().required(),
|
||||||
|
|
||||||
PORT: Joi.number().optional(),
|
PORT: Joi.number().optional(),
|
||||||
});
|
});
|
||||||
|
@ -24,8 +24,6 @@
|
|||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"strictBindCallApply": false,
|
"strictBindCallApply": false,
|
||||||
"forceConsistentCasingInFileNames": false,
|
"forceConsistentCasingInFileNames": false,
|
||||||
"noFallthroughCasesInSwitch": false,
|
"noFallthroughCasesInSwitch": false
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
40
yarn.lock
40
yarn.lock
@ -640,15 +640,6 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/passport-discord@^0.1.14":
|
|
||||||
version "0.1.14"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/passport-discord/-/passport-discord-0.1.14.tgz#a6a8866f88932cc7499cfa325b9f47a4a390e283"
|
|
||||||
integrity sha512-JE7Wbtr4bqqV9poWAbwB+aeVkd3/TM6wgGTtn4Ym6KPLJlJju73BEIH3uS+EeR+D7tY3lP1MtUpJPbxC86PXzA==
|
|
||||||
dependencies:
|
|
||||||
"@types/express" "*"
|
|
||||||
"@types/passport" "*"
|
|
||||||
"@types/passport-oauth2" "*"
|
|
||||||
|
|
||||||
"@types/passport-jwt@^4.0.1":
|
"@types/passport-jwt@^4.0.1":
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/passport-jwt/-/passport-jwt-4.0.1.tgz#080fbe934fb9f6954fb88ec4cdf4bb2cc7c4d435"
|
resolved "https://registry.yarnpkg.com/@types/passport-jwt/-/passport-jwt-4.0.1.tgz#080fbe934fb9f6954fb88ec4cdf4bb2cc7c4d435"
|
||||||
@ -657,7 +648,7 @@
|
|||||||
"@types/jsonwebtoken" "*"
|
"@types/jsonwebtoken" "*"
|
||||||
"@types/passport-strategy" "*"
|
"@types/passport-strategy" "*"
|
||||||
|
|
||||||
"@types/passport-oauth2@*":
|
"@types/passport-oauth2@^1.4.17":
|
||||||
version "1.4.17"
|
version "1.4.17"
|
||||||
resolved "https://registry.yarnpkg.com/@types/passport-oauth2/-/passport-oauth2-1.4.17.tgz#d5d54339d44f6883d03e69dc0cc0e2114067abb4"
|
resolved "https://registry.yarnpkg.com/@types/passport-oauth2/-/passport-oauth2-1.4.17.tgz#d5d54339d44f6883d03e69dc0cc0e2114067abb4"
|
||||||
integrity sha512-ODiAHvso6JcWJ6ZkHHroVp05EHGhqQN533PtFNBkg8Fy5mERDqsr030AX81M0D69ZcaMvhF92SRckEk2B0HYYg==
|
integrity sha512-ODiAHvso6JcWJ6ZkHHroVp05EHGhqQN533PtFNBkg8Fy5mERDqsr030AX81M0D69ZcaMvhF92SRckEk2B0HYYg==
|
||||||
@ -2748,13 +2739,6 @@ parseurl@~1.3.3:
|
|||||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||||
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
|
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
|
||||||
|
|
||||||
passport-discord@^0.1.4:
|
|
||||||
version "0.1.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/passport-discord/-/passport-discord-0.1.4.tgz#9265be11952cdd54d77c47eaae352834444cf0f6"
|
|
||||||
integrity sha512-VJWPYqSOmh7SaCLw/C+k1ZqCzJnn2frrmQRx1YrcPJ3MQ+Oa31XclbbmqFICSvl8xv3Fqd6YWQ4H4p1MpIN9rA==
|
|
||||||
dependencies:
|
|
||||||
passport-oauth2 "^1.5.0"
|
|
||||||
|
|
||||||
passport-jwt@^4.0.1:
|
passport-jwt@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-4.0.1.tgz#c443795eff322c38d173faa0a3c481479646ec3d"
|
resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-4.0.1.tgz#c443795eff322c38d173faa0a3c481479646ec3d"
|
||||||
@ -2763,7 +2747,7 @@ passport-jwt@^4.0.1:
|
|||||||
jsonwebtoken "^9.0.0"
|
jsonwebtoken "^9.0.0"
|
||||||
passport-strategy "^1.0.0"
|
passport-strategy "^1.0.0"
|
||||||
|
|
||||||
passport-oauth2@^1.5.0:
|
passport-oauth2@^1.8.0:
|
||||||
version "1.8.0"
|
version "1.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.8.0.tgz#55725771d160f09bbb191828d5e3d559eee079c8"
|
resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.8.0.tgz#55725771d160f09bbb191828d5e3d559eee079c8"
|
||||||
integrity sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==
|
integrity sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==
|
||||||
@ -3203,7 +3187,16 @@ streamsearch@^1.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
|
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
|
||||||
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
|
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
"string-width-cjs@npm:string-width@^4.2.0":
|
||||||
|
version "4.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
dependencies:
|
||||||
|
emoji-regex "^8.0.0"
|
||||||
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
strip-ansi "^6.0.1"
|
||||||
|
|
||||||
|
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
@ -3235,7 +3228,14 @@ string_decoder@~1.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.1.0"
|
safe-buffer "~5.1.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^5.0.1"
|
||||||
|
|
||||||
|
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
Loading…
Reference in New Issue
Block a user