All files facebook-auth.service.ts

96.15% Statements 25/26
85.71% Branches 6/7
100% Functions 6/6
96% Lines 24/25

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85              1x 2x 2x       2x 2x     2x                         5x 5x 5x 1x     4x       4x   4x       4x 4x                 4x       5x 5x         5x       5x   5x         5x       14x 14x    
import { HttpService, Inject, Injectable } from "@nestjs/common";
import { AxiosError } from "axios";
import { THIRD_PARTY_OPTIONS_PROVIDER } from "./constants";
import { ConfigDetail, FacebookDebugTokenInfo, FacebookUserInfo, IThirdPartyAuthPluginOptions } from "./interfaces";
import path from 'path'
 
@Injectable()
export class FacebookAuthService {
    private endpoint: string = 'https://graph.facebook.com'
    private version: string = 'v16.0'
    private optionDetail: ConfigDetail
 
    constructor(
        @Inject(HttpService) private httpService: HttpService,
        @Inject(THIRD_PARTY_OPTIONS_PROVIDER) private options: Required<IThirdPartyAuthPluginOptions>
        
    ) {
        this.optionDetail = options.facebook
    }
 
    /**
     * The flow to verify a token will be:
     * 
     *  1. Generate app_token using client_id, and client_secret
     *  2. Inspect the token below using app_token
     *  3. Call /me endpoint on behalf of user to get user's email
     * 
     * @param token the token is retrieved from Facebook dialog in front-end
     */
    async verify(token: string) {
        const appToken = await this.generateAppToken()
        const tokenDebugInfo = await this.inspectToken(token, appToken)
        if (!tokenDebugInfo.is_valid) {
            throw new Error('Token is invalid')
        }
 
        Iif (!tokenDebugInfo.scopes.includes('email')) {
            throw new Error('User not grant "email" permission')
        }
 
        const userInfo = await this.getUserInfo(token)
 
        return userInfo
    }
 
    private async getUserInfo(token: string): Promise<FacebookUserInfo> {
        const url = this.constructUrl('me')
        const res = await this.httpService.get<FacebookUserInfo>(url, { 
            params: {
                fields: ['id', 'email', 'first_name', 'last_name'].join(),
            },
            headers: {
                "Authorization": `Bearer ${token}`
            }
        }).toPromise()
 
        return res.data
    }
 
    private async inspectToken(token: string, appToken: string): Promise<FacebookDebugTokenInfo> {
        const url = this.constructUrl('debug_token', false)
        const res = await this.httpService.get<{data: FacebookDebugTokenInfo}>(url, { params: {
            input_token: token,
            access_token: appToken
        } }).toPromise()
 
        return res.data.data
    }
 
    private async generateAppToken(): Promise<string> {
        const url = this.constructUrl('oauth/access_token', false)
        
        const res = await this.httpService.get<{access_token: string}>(url, { params: {
            client_id: this.optionDetail.clientId,
            client_secret: this.optionDetail.clientSecret,
            grant_type: 'client_credentials'
        }}).toPromise()
        return res.data.access_token
    }
    
    private constructUrl(edge: string, withVersion: boolean = true) {
        const url = path.join(this.endpoint, withVersion ? this.version : '', edge)
        return url.toString()
    }
}