feat(api-auth): add oauth strategies for github qq wechat
This commit is contained in:
@@ -3,3 +3,16 @@ AUTH_ACCESS_SECRET="dev-access-secret"
|
||||
AUTH_ACCESS_EXPIRES_IN_SECONDS="900"
|
||||
AUTH_REFRESH_EXPIRES_IN_SECONDS="2592000"
|
||||
AUTH_EMAIL_CODE_TTL_SECONDS="300"
|
||||
OAUTH_GITHUB_CLIENT_ID="github-client-id"
|
||||
OAUTH_GITHUB_CLIENT_SECRET="github-client-secret"
|
||||
OAUTH_GITHUB_CALLBACK_URL="http://localhost:3000/auth/oauth/github/callback"
|
||||
OAUTH_QQ_CLIENT_ID="qq-client-id"
|
||||
OAUTH_QQ_CLIENT_SECRET="qq-client-secret"
|
||||
OAUTH_QQ_CALLBACK_URL="http://localhost:3000/auth/oauth/qq/callback"
|
||||
OAUTH_QQ_AUTH_URL="https://graph.qq.com/oauth2.0/authorize"
|
||||
OAUTH_QQ_TOKEN_URL="https://graph.qq.com/oauth2.0/token"
|
||||
OAUTH_WECHAT_CLIENT_ID="wechat-client-id"
|
||||
OAUTH_WECHAT_CLIENT_SECRET="wechat-client-secret"
|
||||
OAUTH_WECHAT_CALLBACK_URL="http://localhost:3000/auth/oauth/wechat/callback"
|
||||
OAUTH_WECHAT_AUTH_URL="https://open.weixin.qq.com/connect/qrconnect"
|
||||
OAUTH_WECHAT_TOKEN_URL="https://api.weixin.qq.com/sns/oauth2/access_token"
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
"license": "GPL-3.0-or-later",
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.2",
|
||||
"@types/passport-github2": "^1.2.9",
|
||||
"@types/passport-oauth2": "^1.8.0",
|
||||
"dotenv": "^16.6.1",
|
||||
"prisma": "^7.6.0",
|
||||
"ts-node": "^10.9.2",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Body, Controller, Post } from "@nestjs/common";
|
||||
import { Body, Controller, Get, Post, Req, UseGuards } from "@nestjs/common";
|
||||
import { AuthGuard } from "@nestjs/passport";
|
||||
import { AuthService } from "./auth.service";
|
||||
import { EmailLoginDto } from "./dto/email-login.dto";
|
||||
import { RefreshTokenDto } from "./dto/refresh-token.dto";
|
||||
@@ -43,4 +44,58 @@ export class AuthController {
|
||||
async revokeRefreshToken(@Body() body: RefreshTokenDto): Promise<{ success: boolean }> {
|
||||
return this.authService.revokeRefreshToken(body.refreshToken);
|
||||
}
|
||||
|
||||
@Get("oauth/github")
|
||||
@UseGuards(AuthGuard("github"))
|
||||
githubLogin(): void {}
|
||||
|
||||
@Get("oauth/github/callback")
|
||||
@UseGuards(AuthGuard("github"))
|
||||
githubCallback(@Req() req: { user: unknown }): {
|
||||
success: boolean;
|
||||
provider: "github";
|
||||
profile: unknown;
|
||||
} {
|
||||
return {
|
||||
success: true,
|
||||
provider: "github",
|
||||
profile: req.user
|
||||
};
|
||||
}
|
||||
|
||||
@Get("oauth/qq")
|
||||
@UseGuards(AuthGuard("qq"))
|
||||
qqLogin(): void {}
|
||||
|
||||
@Get("oauth/qq/callback")
|
||||
@UseGuards(AuthGuard("qq"))
|
||||
qqCallback(@Req() req: { user: unknown }): {
|
||||
success: boolean;
|
||||
provider: "qq";
|
||||
profile: unknown;
|
||||
} {
|
||||
return {
|
||||
success: true,
|
||||
provider: "qq",
|
||||
profile: req.user
|
||||
};
|
||||
}
|
||||
|
||||
@Get("oauth/wechat")
|
||||
@UseGuards(AuthGuard("wechat"))
|
||||
wechatLogin(): void {}
|
||||
|
||||
@Get("oauth/wechat/callback")
|
||||
@UseGuards(AuthGuard("wechat"))
|
||||
wechatCallback(@Req() req: { user: unknown }): {
|
||||
success: boolean;
|
||||
provider: "wechat";
|
||||
profile: unknown;
|
||||
} {
|
||||
return {
|
||||
success: true,
|
||||
provider: "wechat",
|
||||
profile: req.user
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { ConfigModule, ConfigService } from "@nestjs/config";
|
||||
import { JwtModule } from "@nestjs/jwt";
|
||||
import { PassportModule } from "@nestjs/passport";
|
||||
import { AuthController } from "./auth.controller";
|
||||
import { AuthService } from "./auth.service";
|
||||
import { GithubStrategy } from "./strategies/github.strategy";
|
||||
import { QqStrategy } from "./strategies/qq.strategy";
|
||||
import { WechatStrategy } from "./strategies/wechat.strategy";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule,
|
||||
PassportModule.register({ session: false }),
|
||||
JwtModule.registerAsync({
|
||||
inject: [ConfigService],
|
||||
useFactory: (configService: ConfigService) => {
|
||||
@@ -22,6 +27,6 @@ import { AuthService } from "./auth.service";
|
||||
})
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService]
|
||||
providers: [AuthService, GithubStrategy, QqStrategy, WechatStrategy]
|
||||
})
|
||||
export class AuthModule {}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { PassportStrategy } from "@nestjs/passport";
|
||||
import { Profile, Strategy } from "passport-github2";
|
||||
|
||||
@Injectable()
|
||||
export class GithubStrategy extends PassportStrategy(Strategy, "github") {
|
||||
constructor(configService: ConfigService) {
|
||||
super({
|
||||
clientID: configService.get<string>("OAUTH_GITHUB_CLIENT_ID") ?? "github-client-id",
|
||||
clientSecret:
|
||||
configService.get<string>("OAUTH_GITHUB_CLIENT_SECRET") ?? "github-client-secret",
|
||||
callbackURL:
|
||||
configService.get<string>("OAUTH_GITHUB_CALLBACK_URL") ??
|
||||
"http://localhost:3000/auth/oauth/github/callback",
|
||||
scope: ["user:email"]
|
||||
});
|
||||
}
|
||||
|
||||
async validate(
|
||||
accessToken: string,
|
||||
refreshToken: string,
|
||||
profile: Profile
|
||||
): Promise<{ provider: "github"; accessToken: string; refreshToken: string; profile: Profile }> {
|
||||
return {
|
||||
provider: "github",
|
||||
accessToken,
|
||||
refreshToken,
|
||||
profile
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { PassportStrategy } from "@nestjs/passport";
|
||||
import { Strategy } from "passport-oauth2";
|
||||
|
||||
@Injectable()
|
||||
export class QqStrategy extends PassportStrategy(Strategy, "qq") {
|
||||
constructor(configService: ConfigService) {
|
||||
super({
|
||||
authorizationURL:
|
||||
configService.get<string>("OAUTH_QQ_AUTH_URL") ?? "https://graph.qq.com/oauth2.0/authorize",
|
||||
tokenURL:
|
||||
configService.get<string>("OAUTH_QQ_TOKEN_URL") ?? "https://graph.qq.com/oauth2.0/token",
|
||||
clientID: configService.get<string>("OAUTH_QQ_CLIENT_ID") ?? "qq-client-id",
|
||||
clientSecret: configService.get<string>("OAUTH_QQ_CLIENT_SECRET") ?? "qq-client-secret",
|
||||
callbackURL:
|
||||
configService.get<string>("OAUTH_QQ_CALLBACK_URL") ??
|
||||
"http://localhost:3000/auth/oauth/qq/callback",
|
||||
scope: ["get_user_info"]
|
||||
});
|
||||
}
|
||||
|
||||
async validate(
|
||||
accessToken: string,
|
||||
refreshToken: string
|
||||
): Promise<{ provider: "qq"; accessToken: string; refreshToken: string }> {
|
||||
return {
|
||||
provider: "qq",
|
||||
accessToken,
|
||||
refreshToken
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { PassportStrategy } from "@nestjs/passport";
|
||||
import { Strategy } from "passport-oauth2";
|
||||
|
||||
@Injectable()
|
||||
export class WechatStrategy extends PassportStrategy(Strategy, "wechat") {
|
||||
constructor(configService: ConfigService) {
|
||||
super({
|
||||
authorizationURL:
|
||||
configService.get<string>("OAUTH_WECHAT_AUTH_URL") ??
|
||||
"https://open.weixin.qq.com/connect/qrconnect",
|
||||
tokenURL:
|
||||
configService.get<string>("OAUTH_WECHAT_TOKEN_URL") ??
|
||||
"https://api.weixin.qq.com/sns/oauth2/access_token",
|
||||
clientID: configService.get<string>("OAUTH_WECHAT_CLIENT_ID") ?? "wechat-client-id",
|
||||
clientSecret:
|
||||
configService.get<string>("OAUTH_WECHAT_CLIENT_SECRET") ?? "wechat-client-secret",
|
||||
callbackURL:
|
||||
configService.get<string>("OAUTH_WECHAT_CALLBACK_URL") ??
|
||||
"http://localhost:3000/auth/oauth/wechat/callback",
|
||||
scope: ["snsapi_login"]
|
||||
});
|
||||
}
|
||||
|
||||
async validate(
|
||||
accessToken: string,
|
||||
refreshToken: string
|
||||
): Promise<{ provider: "wechat"; accessToken: string; refreshToken: string }> {
|
||||
return {
|
||||
provider: "wechat",
|
||||
accessToken,
|
||||
refreshToken
|
||||
};
|
||||
}
|
||||
}
|
||||
Generated
+116
@@ -43,6 +43,9 @@ importers:
|
||||
"@nestjs/jwt":
|
||||
specifier: ^11.0.2
|
||||
version: 11.0.2(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))
|
||||
"@nestjs/passport":
|
||||
specifier: ^11.0.5
|
||||
version: 11.0.5(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)
|
||||
"@nestjs/platform-express":
|
||||
specifier: ^11.1.18
|
||||
version: 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)
|
||||
@@ -55,6 +58,15 @@ importers:
|
||||
class-validator:
|
||||
specifier: ^0.15.1
|
||||
version: 0.15.1
|
||||
passport:
|
||||
specifier: ^0.7.0
|
||||
version: 0.7.0
|
||||
passport-github2:
|
||||
specifier: ^0.1.12
|
||||
version: 0.1.12
|
||||
passport-oauth2:
|
||||
specifier: ^1.8.0
|
||||
version: 1.8.0
|
||||
reflect-metadata:
|
||||
specifier: ^0.2.2
|
||||
version: 0.2.2
|
||||
@@ -320,6 +332,15 @@ packages:
|
||||
peerDependencies:
|
||||
"@nestjs/common": ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
|
||||
|
||||
"@nestjs/passport@11.0.5":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==
|
||||
}
|
||||
peerDependencies:
|
||||
"@nestjs/common": ^10.0.0 || ^11.0.0
|
||||
passport: ^0.5.0 || ^0.6.0 || ^0.7.0
|
||||
|
||||
"@nestjs/platform-express@11.1.18":
|
||||
resolution:
|
||||
{
|
||||
@@ -787,6 +808,13 @@ packages:
|
||||
}
|
||||
engines: { node: 18 || 20 || >=22 }
|
||||
|
||||
base64url@3.0.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
|
||||
}
|
||||
engines: { node: ">=6.0.0" }
|
||||
|
||||
better-result@2.7.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -2002,6 +2030,12 @@ packages:
|
||||
engines: { node: ">=18" }
|
||||
hasBin: true
|
||||
|
||||
oauth@0.10.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==
|
||||
}
|
||||
|
||||
object-assign@4.1.1:
|
||||
resolution:
|
||||
{
|
||||
@@ -2070,6 +2104,34 @@ packages:
|
||||
}
|
||||
engines: { node: ">= 0.8" }
|
||||
|
||||
passport-github2@0.1.12:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-3nPUCc7ttF/3HSP/k9sAXjz3SkGv5Nki84I05kSQPo01Jqq1NzJACgMblCK0fGcv9pKCG/KXU3AJRDGLqHLoIw==
|
||||
}
|
||||
engines: { node: ">= 0.8.0" }
|
||||
|
||||
passport-oauth2@1.8.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==
|
||||
}
|
||||
engines: { node: ">= 0.4.0" }
|
||||
|
||||
passport-strategy@1.0.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==
|
||||
}
|
||||
engines: { node: ">= 0.4.0" }
|
||||
|
||||
passport@0.7.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==
|
||||
}
|
||||
engines: { node: ">= 0.4.0" }
|
||||
|
||||
path-exists@4.0.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -2109,6 +2171,12 @@ packages:
|
||||
integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==
|
||||
}
|
||||
|
||||
pause@0.0.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==
|
||||
}
|
||||
|
||||
perfect-debounce@1.0.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -2685,6 +2753,12 @@ packages:
|
||||
engines: { node: ">=14.17" }
|
||||
hasBin: true
|
||||
|
||||
uid2@0.0.4:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==
|
||||
}
|
||||
|
||||
uid@2.0.2:
|
||||
resolution:
|
||||
{
|
||||
@@ -2724,6 +2798,13 @@ packages:
|
||||
integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
||||
}
|
||||
|
||||
utils-merge@1.0.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
|
||||
}
|
||||
engines: { node: ">= 0.4.0" }
|
||||
|
||||
v8-compile-cache-lib@3.0.1:
|
||||
resolution:
|
||||
{
|
||||
@@ -2951,6 +3032,11 @@ snapshots:
|
||||
"@types/jsonwebtoken": 9.0.10
|
||||
jsonwebtoken: 9.0.3
|
||||
|
||||
"@nestjs/passport@11.0.5(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)":
|
||||
dependencies:
|
||||
"@nestjs/common": 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
passport: 0.7.0
|
||||
|
||||
"@nestjs/platform-express@11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)":
|
||||
dependencies:
|
||||
"@nestjs/common": 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
@@ -3223,6 +3309,8 @@ snapshots:
|
||||
|
||||
balanced-match@4.0.4: {}
|
||||
|
||||
base64url@3.0.1: {}
|
||||
|
||||
better-result@2.7.0:
|
||||
dependencies:
|
||||
"@clack/prompts": 0.11.0
|
||||
@@ -3919,6 +4007,8 @@ snapshots:
|
||||
pathe: 2.0.3
|
||||
tinyexec: 1.0.4
|
||||
|
||||
oauth@0.10.2: {}
|
||||
|
||||
object-assign@4.1.1: {}
|
||||
|
||||
object-inspect@1.13.4: {}
|
||||
@@ -3956,6 +4046,26 @@ snapshots:
|
||||
|
||||
parseurl@1.3.3: {}
|
||||
|
||||
passport-github2@0.1.12:
|
||||
dependencies:
|
||||
passport-oauth2: 1.8.0
|
||||
|
||||
passport-oauth2@1.8.0:
|
||||
dependencies:
|
||||
base64url: 3.0.1
|
||||
oauth: 0.10.2
|
||||
passport-strategy: 1.0.0
|
||||
uid2: 0.0.4
|
||||
utils-merge: 1.0.1
|
||||
|
||||
passport-strategy@1.0.0: {}
|
||||
|
||||
passport@0.7.0:
|
||||
dependencies:
|
||||
passport-strategy: 1.0.0
|
||||
pause: 0.0.1
|
||||
utils-merge: 1.0.1
|
||||
|
||||
path-exists@4.0.0: {}
|
||||
|
||||
path-is-absolute@1.0.1: {}
|
||||
@@ -3968,6 +4078,8 @@ snapshots:
|
||||
|
||||
pathe@2.0.3: {}
|
||||
|
||||
pause@0.0.1: {}
|
||||
|
||||
perfect-debounce@1.0.0: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
@@ -4318,6 +4430,8 @@ snapshots:
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
uid2@0.0.4: {}
|
||||
|
||||
uid@2.0.2:
|
||||
dependencies:
|
||||
"@lukeed/csprng": 1.1.0
|
||||
@@ -4334,6 +4448,8 @@ snapshots:
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
utils-merge@1.0.1: {}
|
||||
|
||||
v8-compile-cache-lib@3.0.1: {}
|
||||
|
||||
valibot@1.2.0(typescript@5.9.3):
|
||||
|
||||
Reference in New Issue
Block a user