UNPKG

@rytass/member-base-nestjs-module

Version:

Rytass Member System NestJS Base Module

199 lines (196 loc) 8.18 kB
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 };