import jwt from 'jsonwebtoken' ; import { nanoid } from 'nanoid' ; import { NODE_TYPES , OAUTH_ACCESS_TOKEN_EXPIRES_IN_MS , OAUTH_REFRESH_TOKEN_EXPIRES_IN_MS , } from 'common/enums' ; import { environment } from 'common/environment' ; import logger from 'common/logger' ; import { isScopeAllowed , toGlobalId } from 'common/utils' ; import { BaseService , UserService } from 'connectors' ; import { Falsey , OAuthAuthorizationCode , OAuthClient , OAuthRefreshToken , OAuthToken , User , } from 'definitions' ; export class OAuthService extends BaseService { constructor ( ) { super ( 'oauth_client' ) ; } /*********************************
     *                               *
     *             Client            *
     *                               *
     *********************************/ findClient = async ( { clientId } ) => { return this . knex ( 'oauth_client' ) . select ( ) . where ( { clientId } ) . first ( ) ; } ; findClientByName = async ( { name } ) => { return this . knex ( 'oauth_client' ) . select ( ) . where ( { name } ) . first ( ) ; } ; updateOrCreateClient = async ( params ) => { return this . baseUpdateOrCreate ( { where : { clientId : params . clientId } , data : { ... params , updatedAt : new Date ( ) , } , table : 'oauth_client' , } ) ; } ; getClient = async ( clientId , clientSecret ? ) : Promise < OAuthClient | Falsey > => { const client = await this . knex ( 'oauth_client' ) . select ( ) . where ( { clientId , ... ( clientSecret ? { clientSecret } : { } ) } ) . first ( ) ; return this . toOAuthClient ( client ) ; } ; toOAuthClient = ( dbClient ) => { if ( ! dbClient ) { return ; } return { id : dbClient . id , redirectUris : dbClient . redirectUri , grants : dbClient . grantTypes , scope : dbClient . scope , rawClient : dbClient , } ; } ; getClientById = async ( id ) => { const client = await this . knex ( 'oauth_client' ) . select ( ) . where ( { id } ) . first ( ) ; return this . toOAuthClient ( client ) ; } ; /*********************************
     *                               *
     *          Access Token         *
     *                               *
     *********************************/ generateAccessToken = async ( client , user , scope ) : Promise < string > => { return nanoid ( 40 ) ; } ; getAccessToken = async ( accessToken ) : Promise < OAuthToken | Falsey > => { const token = await this . knex ( 'oauth_access_token' ) . select ( ) . where ( { token : accessToken } ) . first ( ) ; if ( ! token ) { return ; } const client = ( await this . getClientById ( token . clientId ) ) as OAuthClient ; const userService = new UserService ( ) ; const user = ( await userService . dataloader . load ( token . userId ) ) as User ; return { accessToken : token . token , accessTokenExpiresAt : new Date ( token . expires ) , scope : token . scope , client , user , } ; } ; saveToken = async ( token , client , user ) : Promise < OAuthToken > => { const scope = token . scope instanceof Array ? token . scope : [ token . scope ] ; const accessToken = await this . baseCreate ( { token : token . accessToken , expires : token . accessTokenExpiresAt , clientId : client . id , userId : user . id , scope , } , 'oauth_access_token' ) ; const refreshToken = await this . baseCreate ( { token : token . refreshToken , expires : token . refreshTokenExpiresAt , clientId : client . id , userId : user . id , scope , } , 'oauth_refresh_token' ) ; const payload = { sub : toGlobalId ( { type : NODE_TYPES . User , id : user . id } ) , name : user . userName , email : user . email , emailVerified : user . emailVerified , } ; const id_token = jwt . sign ( payload , environment . OICDPrivateKey , { expiresIn : OAUTH_ACCESS_TOKEN_EXPIRES_IN_MS / 1000 , issuer : 'matters.news' , algorithm : 'RS256' , } ) ; return { accessToken : accessToken . token , accessTokenExpiresAt : new Date ( accessToken . expires ) , refreshToken : refreshToken . token , refreshTokenExpiresAt : new Date ( refreshToken . expires ) , scope : accessToken . scope , id_token , client , user , } ; } ; /*********************************
     *                               *
     *       Authorization Code      *
     *                               *
     *********************************/ getAuthorizationCode = async ( authorizationCode ) : Promise < OAuthAuthorizationCode | Falsey > => { const code = await this . knex ( 'oauth_authorization_code' ) . select ( ) . where ( { code : authorizationCode } ) . first ( ) ; const client = ( await this . getClientById ( code . clientId ) ) as OAuthClient ; const userService = new UserService ( ) ; const user = ( await userService . dataloader . load ( code . userId ) ) as User ; if ( ! code ) { return ; } return { authorizationCode : code . code , expiresAt : new Date ( code . expires ) , redirectUri : code . redirectUri , scope : code . scope , client , user , } ; } ; saveAuthorizationCode = async ( code , client , user ) : Promise < OAuthAuthorizationCode | Falsey > => { const authorizationCode = await this . baseCreate ( { code : code . authorizationCode , expires : code . expiresAt , redirect_uri : code . redirectUri , scope : code . scope , clientId : client . id , userId : user . id , } , 'oauth_authorization_code' ) ; if ( ! authorizationCode ) { return ; } return { authorizationCode : authorizationCode . code , expiresAt : new Date ( authorizationCode . exipres ) , redirectUri : authorizationCode . redirectUri , scope : authorizationCode . scope , client , user , } ; } ; revokeAuthorizationCode = async ( code ) => { try { await this . knex ( 'oauth_authorization_code' ) . select ( ) . where ( { code : code . authorizationCode , userId : code . user . id } ) . del ( ) ; return true ; } catch ( e ) { logger . error ( e ) ; return false ; } } ; /*********************************
     *                               *
     *         Refresh Token         *
     *                               *
     *********************************/ generateRefreshToken = async ( client , user , scope ) : Promise < string > => { return nanoid ( 40 ) ; } ; getRefreshToken = async ( refreshToken ) : Promise < OAuthRefreshToken | Falsey > => { const token = await this . knex ( 'oauth_refresh_token' ) . select ( ) . where ( { token : refreshToken } ) . first ( ) ; const client = ( await this . getClientById ( token . clientId ) ) as OAuthClient ; const userService = new UserService ( ) ; const user = ( await userService . dataloader . load ( token . userId ) ) as User ; if ( ! token ) { return ; } return { refreshToken : token . token , refreshTokenExpiresAt : new Date ( token . expires ) , scope : token . scope , client , user , } ; } ; revokeToken = async ( token ) => { try { await this . knex ( 'oauth_refresh_token' ) . select ( ) . where ( { token : token . refreshToken , userId : token . user . id } ) . del ( ) ; return true ; } catch ( e ) { logger . error ( e ) ; return false ; } } ; /*********************************
     *                               *
     *             Scope             *
     *                               *
     *********************************/ scopeStr2Arr = ( scope ) : string [ ] => { return scope . split ( /[,\s]/ ) . filter ( ( s ) => ! ! s ) ; } ; validateScope = async ( user , client , scope ) : Promise < string | string [ ] | Falsey > => { if ( ! client . scope ) { return false ; } if ( ! scope ) { return client . scope ; } const clientScope = client . scope as string [ ] ; const scopes = scope instanceof Array ? scope : this . scopeStr2Arr ( scope ) ; let isValidate = true ; for ( const requestScope of scopes ) { if ( ! isScopeAllowed ( clientScope , requestScope ) ) { isValidate = false ; break ; } } if ( ! isValidate ) { return false ; } return scopes ; } ; verifyScope = async ( accessToken , scope ) => { return true ; } ; /*********************************
     *                               *
     *           LikeCoin            *
     *                               *
     *********************************/ generateTokenForLikeCoin = async ( { userId } ) => { const userService = new UserService ( ) ; const user = ( await userService . dataloader . load ( userId ) ) as User ; const name = environment . likecoinOAuthClientName ; const client = await this . findClientByName ( { name } ) ; const oauthClient = this . toOAuthClient ( client ) ; if ( ! client || ! oauthClient ) { throw new Error ( `client of "${ name }" does not exists` ) ; } const accessToken = await this . generateAccessToken ( oauthClient , user , client . scope ) ; const refreshToken = await this . generateRefreshToken ( oauthClient , user , client . scope ) ; return this . saveToken ( { accessToken , accessTokenExpiresAt : new Date ( Date . now ( ) + OAUTH_ACCESS_TOKEN_EXPIRES_IN_MS ) , refreshToken , refreshTokenExpiresAt : new Date ( Date . now ( ) + OAUTH_REFRESH_TOKEN_EXPIRES_IN_MS ) , scope : client . scope , client , user , } , client , user ) ; } ; }