@rytass/member-base-nestjs-module
Version:
Rytass Member System NestJS Base Module
199 lines (196 loc) • 8.18 kB
JavaScript
import { Injectable, Inject, BadRequestException } from '@nestjs/common';
import axios from 'axios';
import { OAUTH2_PROVIDERS, RESOLVED_MEMBER_REPO } from '../typings/member-base-providers.js';
import { MemberOAuthRecordRepo } from '../models/member-oauth-record.entity.js';
import { MemberBaseService } from './member-base.service.js';
function _ts_decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function _ts_param(paramIndex, decorator) {
return function(target, key) {
decorator(target, key, paramIndex);
};
}
class OAuthService {
providers;
baseMemberRepo;
memberOAuthRecordRepo;
memberBaseService;
constructor(providers, baseMemberRepo, memberOAuthRecordRepo, memberBaseService){
this.providers = providers;
this.baseMemberRepo = baseMemberRepo;
this.memberOAuthRecordRepo = memberOAuthRecordRepo;
this.memberBaseService = memberBaseService;
}
async getGoogleOAuthLoginUrl() {
const provider = this.providers.find((p)=>p.channel === 'google');
if (!provider) {
throw new BadRequestException('Google OAuth2 provider not found');
}
const state = await provider.getState?.() ?? null;
const params = new URLSearchParams({
client_id: provider.clientId,
redirect_uri: provider.redirectUri,
response_type: 'code',
scope: (provider.scope ?? [
'openid',
'email'
]).join(' '),
...state ? {
state
} : {}
});
return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
}
async getFacebookOAuthLoginUrl() {
const provider = this.providers.find((p)=>p.channel === 'facebook');
if (!provider) {
throw new BadRequestException('Facebook OAuth2 provider not found');
}
const state = await provider.getState?.() ?? null;
const params = new URLSearchParams({
client_id: provider.clientId,
redirect_uri: provider.redirectUri,
response_type: 'code',
scope: (provider.scope ?? [
'public_profile',
'email'
]).join(' '),
...state ? {
state
} : {}
});
return `https://www.facebook.com/v22.0/dialog/oauth?${params.toString()}`;
}
async getCustomOAuthLoginUrl(channel) {
const provider = this.providers.find((p)=>p.channel === channel);
if (!provider) {
throw new BadRequestException('OAuth2 provider not found');
}
const state = await provider.getState?.() ?? null;
const params = new URLSearchParams({
client_id: provider.clientId,
redirect_uri: provider.redirectUri,
response_type: 'code',
scope: provider.scope.join(' '),
...state ? {
state
} : {}
});
return `${provider.requestUrl}?${params.toString()}`;
}
async loginWithGoogleOAuth2Code(code, state) {
const provider = this.providers.find((p)=>p.channel === 'google');
if (!provider) {
throw new BadRequestException('Google OAuth2 provider not found');
}
const { data: { access_token } } = await axios.post('https://oauth2.googleapis.com/token', {
code,
client_id: provider.clientId,
client_secret: provider.clientSecret,
redirect_uri: provider.redirectUri,
grant_type: 'authorization_code'
});
const { data } = await axios.get('https://openidconnect.googleapis.com/v1/userinfo', {
headers: {
Authorization: `Bearer ${access_token}`
}
});
if (!data.email_verified) {
throw new BadRequestException('Email not verified');
}
const tokenPair = await this.loginWithOAuthIdentifier('google', data.email.toLowerCase());
return {
...tokenPair,
state
};
}
async loginWithFacebookOAuth2Code(code, state) {
const provider = this.providers.find((p)=>p.channel === 'facebook');
if (!provider) {
throw new BadRequestException('Facebook OAuth2 provider not found');
}
const { data: { access_token } } = await axios.post('https://graph.facebook.com/v22.0/oauth/access_token', {
code,
client_id: provider.clientId,
client_secret: provider.clientSecret,
redirect_uri: provider.redirectUri
});
const { data } = await axios.get(`https://graph.facebook.com/me?fields=id,name,email&access_token=${access_token}`);
const tokenPair = await this.loginWithOAuthIdentifier('facebook', data.email.toLowerCase());
return {
...tokenPair,
state
};
}
async loginWithCustomOAuth2Code(channel, code, state) {
const provider = this.providers.find((p)=>p.channel === channel);
if (!provider) {
throw new BadRequestException('OAuth2 provider not found');
}
const accessToken = await provider.getAccessTokenFromCode(code);
const account = await provider.getAccountFromAccessToken(accessToken);
const tokenPair = await this.loginWithOAuthIdentifier(channel, account);
return {
...tokenPair,
state
};
}
async loginWithOAuthIdentifier(channel, identifier) {
const qb = this.memberOAuthRecordRepo.createQueryBuilder('oauthRecords');
qb.innerJoinAndSelect('oauthRecords.member', 'member');
qb.andWhere('oauthRecords.channel = :channel', {
channel
});
qb.andWhere('oauthRecords.channelIdentifier = :channelIdentifier', {
channelIdentifier: identifier
});
const oauthRecord = await qb.getOne();
if (oauthRecord) {
return {
accessToken: this.memberBaseService.signAccessToken(oauthRecord.member),
refreshToken: this.memberBaseService.signRefreshToken(oauthRecord.member)
};
} else {
const existedUnbindMember = await this.baseMemberRepo.findOne({
where: {
account: identifier
}
});
if (existedUnbindMember) {
await this.memberOAuthRecordRepo.save({
memberId: existedUnbindMember.id,
channel,
channelIdentifier: identifier
});
return {
accessToken: this.memberBaseService.signAccessToken(existedUnbindMember),
refreshToken: this.memberBaseService.signRefreshToken(existedUnbindMember)
};
}
const [member] = await this.memberBaseService.registerWithoutPassword(identifier, {
shouldUpdatePassword: false
});
await this.memberOAuthRecordRepo.save({
memberId: member.id,
channel,
channelIdentifier: identifier
});
return {
accessToken: this.memberBaseService.signAccessToken(member),
refreshToken: this.memberBaseService.signRefreshToken(member)
};
}
}
}
OAuthService = _ts_decorate([
Injectable(),
_ts_param(0, Inject(OAUTH2_PROVIDERS)),
_ts_param(1, Inject(RESOLVED_MEMBER_REPO)),
_ts_param(2, Inject(MemberOAuthRecordRepo)),
_ts_param(3, Inject(MemberBaseService))
], OAuthService);
export { OAuthService };