2021-07-17 21:59:50 +05:30
package handlers
import (
2021-12-03 22:55:27 +05:30
"context"
2022-06-15 21:55:41 +05:30
"encoding/base64"
2021-07-17 21:59:50 +05:30
"encoding/json"
"fmt"
2022-12-23 18:19:44 +01:00
"io"
2021-07-17 21:59:50 +05:30
"net/http"
2022-03-08 12:36:26 +05:30
"strconv"
2021-07-17 21:59:50 +05:30
"strings"
"time"
2023-12-02 12:21:53 +05:30
"golang.org/x/oauth2"
2022-05-23 11:52:51 +05:30
"github.com/coreos/go-oidc/v3/oidc"
"github.com/gin-gonic/gin"
2022-10-23 21:08:08 +05:30
"github.com/google/uuid"
2023-12-02 12:21:53 +05:30
2022-05-23 11:52:51 +05:30
log "github.com/sirupsen/logrus"
2021-07-23 21:57:44 +05:30
"github.com/authorizerdev/authorizer/server/constants"
2022-01-23 01:24:41 +05:30
"github.com/authorizerdev/authorizer/server/cookie"
2021-07-23 21:57:44 +05:30
"github.com/authorizerdev/authorizer/server/db"
2022-01-21 13:34:04 +05:30
"github.com/authorizerdev/authorizer/server/db/models"
2022-05-27 23:20:38 +05:30
"github.com/authorizerdev/authorizer/server/memorystore"
2021-07-23 21:57:44 +05:30
"github.com/authorizerdev/authorizer/server/oauth"
2023-10-26 00:55:10 +05:30
"github.com/authorizerdev/authorizer/server/refs"
2022-01-23 01:24:41 +05:30
"github.com/authorizerdev/authorizer/server/token"
2021-07-23 21:57:44 +05:30
"github.com/authorizerdev/authorizer/server/utils"
2021-07-17 21:59:50 +05:30
)
2022-01-17 11:32:13 +05:30
// OAuthCallbackHandler handles the OAuth callback for various oauth providers
func OAuthCallbackHandler ( ) gin . HandlerFunc {
2022-07-10 21:49:33 +05:30
return func ( ctx * gin . Context ) {
provider := ctx . Param ( "oauth_provider" )
state := ctx . Request . FormValue ( "state" )
2022-05-29 17:22:46 +05:30
sessionState , err := memorystore . Provider . GetState ( state )
if sessionState == "" || err != nil {
2022-05-25 12:30:22 +05:30
log . Debug ( "Invalid oauth state: " , state )
2022-07-10 21:49:33 +05:30
ctx . JSON ( 400 , gin . H { "error" : "invalid oauth state" } )
2023-08-17 14:20:31 +05:30
return
2022-01-17 11:32:13 +05:30
}
// contains random token, redirect url, role
2022-03-08 19:13:45 +05:30
sessionSplit := strings . Split ( state , "___" )
2022-01-17 11:32:13 +05:30
2022-03-08 12:36:26 +05:30
if len ( sessionSplit ) < 3 {
2022-05-25 12:30:22 +05:30
log . Debug ( "Unable to get redirect url from state: " , state )
2022-07-10 21:49:33 +05:30
ctx . JSON ( 400 , gin . H { "error" : "invalid redirect url" } )
2022-01-17 11:32:13 +05:30
return
}
2022-06-12 00:27:21 +05:30
// remove state from store
go memorystore . Provider . RemoveState ( state )
2022-03-08 12:36:26 +05:30
stateValue := sessionSplit [ 0 ]
2022-01-17 11:32:13 +05:30
redirectURL := sessionSplit [ 1 ]
2022-03-08 12:36:26 +05:30
inputRoles := strings . Split ( sessionSplit [ 2 ] , "," )
scopes := strings . Split ( sessionSplit [ 3 ] , "," )
2023-07-31 16:42:11 +05:30
var user * models . User
2022-11-15 21:45:08 +05:30
oauthCode := ctx . Request . FormValue ( "code" )
2023-08-17 14:20:31 +05:30
if oauthCode == "" {
log . Debug ( "Invalid oauth code: " , oauthCode )
ctx . JSON ( 400 , gin . H { "error" : "invalid oauth code" } )
return
}
2022-01-17 11:32:13 +05:30
switch provider {
2022-06-29 22:24:00 +05:30
case constants . AuthRecipeMethodGoogle :
2023-08-17 14:20:31 +05:30
user , err = processGoogleUserInfo ( ctx , oauthCode )
2022-06-29 22:24:00 +05:30
case constants . AuthRecipeMethodGithub :
2023-08-17 14:20:31 +05:30
user , err = processGithubUserInfo ( ctx , oauthCode )
2022-06-29 22:24:00 +05:30
case constants . AuthRecipeMethodFacebook :
2023-08-17 14:20:31 +05:30
user , err = processFacebookUserInfo ( ctx , oauthCode )
2022-06-29 22:24:00 +05:30
case constants . AuthRecipeMethodLinkedIn :
2023-08-17 14:20:31 +05:30
user , err = processLinkedInUserInfo ( ctx , oauthCode )
2022-06-29 22:24:00 +05:30
case constants . AuthRecipeMethodApple :
2023-08-17 14:20:31 +05:30
user , err = processAppleUserInfo ( ctx , oauthCode )
2022-08-13 12:35:00 +05:30
case constants . AuthRecipeMethodTwitter :
2023-08-17 14:20:31 +05:30
user , err = processTwitterUserInfo ( ctx , oauthCode , sessionState )
2023-02-26 05:23:02 +05:30
case constants . AuthRecipeMethodMicrosoft :
2023-08-17 14:20:31 +05:30
user , err = processMicrosoftUserInfo ( ctx , oauthCode )
2023-12-02 12:21:53 +05:30
case constants . AuthRecipeMethodTwitch :
user , err = processTwitchUserInfo ( ctx , oauthCode )
2022-01-17 11:32:13 +05:30
default :
2022-05-23 11:52:51 +05:30
log . Info ( "Invalid oauth provider" )
2022-01-17 11:32:13 +05:30
err = fmt . Errorf ( ` invalid oauth provider ` )
}
if err != nil {
2022-05-23 11:52:51 +05:30
log . Debug ( "Failed to process user info: " , err )
2022-07-10 21:49:33 +05:30
ctx . JSON ( 400 , gin . H { "error" : err . Error ( ) } )
2022-01-17 11:32:13 +05:30
return
}
2023-10-26 00:55:10 +05:30
existingUser , err := db . Provider . GetUserByEmail ( ctx , refs . StringValue ( user . Email ) )
2022-05-23 11:52:51 +05:30
log := log . WithField ( "user" , user . Email )
2022-07-11 10:42:42 +05:30
isSignUp := false
2022-01-17 11:32:13 +05:30
if err != nil {
2022-05-29 17:22:46 +05:30
isSignupDisabled , err := memorystore . Provider . GetBoolStoreEnvVariable ( constants . EnvKeyDisableSignUp )
if err != nil {
log . Debug ( "Failed to get signup disabled env variable: " , err )
2022-07-10 21:49:33 +05:30
ctx . JSON ( 400 , gin . H { "error" : err . Error ( ) } )
2022-05-29 17:22:46 +05:30
return
}
if isSignupDisabled {
2022-05-23 11:52:51 +05:30
log . Debug ( "Failed to signup as disabled" )
2022-07-10 21:49:33 +05:30
ctx . JSON ( 400 , gin . H { "error" : "signup is disabled for this instance" } )
2022-03-16 22:49:18 +05:30
return
}
2022-01-17 11:32:13 +05:30
// user not registered, register user and generate session token
user . SignupMethods = provider
// make sure inputRoles don't include protected roles
hasProtectedRole := false
for _ , ir := range inputRoles {
2022-05-31 08:14:03 +05:30
protectedRolesString , err := memorystore . Provider . GetStringStoreEnvVariable ( constants . EnvKeyProtectedRoles )
protectedRoles := [ ] string { }
2022-05-29 17:22:46 +05:30
if err != nil {
log . Debug ( "Failed to get protected roles: " , err )
2022-05-31 08:14:03 +05:30
protectedRolesString = ""
} else {
protectedRoles = strings . Split ( protectedRolesString , "," )
2022-05-29 17:22:46 +05:30
}
if utils . StringSliceContains ( protectedRoles , ir ) {
2022-01-17 11:32:13 +05:30
hasProtectedRole = true
}
}
if hasProtectedRole {
2022-05-25 12:30:22 +05:30
log . Debug ( "Signup is not allowed with protected roles:" , inputRoles )
2022-07-10 21:49:33 +05:30
ctx . JSON ( 400 , gin . H { "error" : "invalid role" } )
2022-01-17 11:32:13 +05:30
return
}
user . Roles = strings . Join ( inputRoles , "," )
now := time . Now ( ) . Unix ( )
user . EmailVerifiedAt = & now
2022-07-10 21:49:33 +05:30
user , _ = db . Provider . AddUser ( ctx , user )
2022-07-11 10:42:42 +05:30
isSignUp = true
2022-01-17 11:32:13 +05:30
} else {
2022-06-12 00:27:21 +05:30
user = existingUser
2022-03-24 14:13:55 +05:30
if user . RevokedTimestamp != nil {
2022-05-25 12:30:22 +05:30
log . Debug ( "User access revoked at: " , user . RevokedTimestamp )
2022-07-10 21:49:33 +05:30
ctx . JSON ( 400 , gin . H { "error" : "user access has been revoked" } )
2022-06-12 00:27:21 +05:30
return
2022-03-24 14:13:55 +05:30
}
2022-01-17 11:32:13 +05:30
// user exists in db, check if method was google
// if not append google to existing signup method and save it
signupMethod := existingUser . SignupMethods
if ! strings . Contains ( signupMethod , provider ) {
signupMethod = signupMethod + "," + provider
}
user . SignupMethods = signupMethod
2022-01-31 11:35:24 +05:30
if user . EmailVerifiedAt == nil {
now := time . Now ( ) . Unix ( )
user . EmailVerifiedAt = & now
}
2022-01-17 11:32:13 +05:30
// There multiple scenarios with roles here in social login
// 1. user has access to protected roles + roles and trying to login
// 2. user has not signed up for one of the available role but trying to signup.
// Need to modify roles in this case
// find the unassigned roles
existingRoles := strings . Split ( existingUser . Roles , "," )
unasignedRoles := [ ] string { }
for _ , ir := range inputRoles {
if ! utils . StringSliceContains ( existingRoles , ir ) {
unasignedRoles = append ( unasignedRoles , ir )
}
}
if len ( unasignedRoles ) > 0 {
// check if it contains protected unassigned role
hasProtectedRole := false
for _ , ur := range unasignedRoles {
2022-05-31 08:14:03 +05:30
protectedRolesString , err := memorystore . Provider . GetStringStoreEnvVariable ( constants . EnvKeyProtectedRoles )
protectedRoles := [ ] string { }
2022-05-29 17:22:46 +05:30
if err != nil {
log . Debug ( "Failed to get protected roles: " , err )
2022-05-31 08:14:03 +05:30
protectedRolesString = ""
} else {
protectedRoles = strings . Split ( protectedRolesString , "," )
2022-05-29 17:22:46 +05:30
}
if utils . StringSliceContains ( protectedRoles , ur ) {
2022-01-17 11:32:13 +05:30
hasProtectedRole = true
}
}
if hasProtectedRole {
2022-05-23 11:52:51 +05:30
log . Debug ( "Invalid role. User is using protected unassigned role" )
2022-07-10 21:49:33 +05:30
ctx . JSON ( 400 , gin . H { "error" : "invalid role" } )
2022-01-17 11:32:13 +05:30
return
} else {
user . Roles = existingUser . Roles + "," + strings . Join ( unasignedRoles , "," )
}
} else {
user . Roles = existingUser . Roles
}
2022-02-02 12:30:11 +05:30
2022-07-10 21:49:33 +05:30
user , err = db . Provider . UpdateUser ( ctx , user )
2022-02-02 12:30:11 +05:30
if err != nil {
2022-05-23 11:52:51 +05:30
log . Debug ( "Failed to update user: " , err )
2022-07-10 21:49:33 +05:30
ctx . JSON ( 500 , gin . H { "error" : err . Error ( ) } )
2022-02-02 12:30:11 +05:30
return
}
2022-01-17 11:32:13 +05:30
}
2022-11-13 01:22:21 +05:30
// TODO
// use stateValue to get code / nonce
// add code / nonce to id_token
2022-11-15 21:45:08 +05:30
code := ""
codeChallenge := ""
nonce := ""
if stateValue != "" {
// Get state from store
authorizeState , _ := memorystore . Provider . GetState ( stateValue )
if authorizeState != "" {
authorizeStateSplit := strings . Split ( authorizeState , "@@" )
if len ( authorizeStateSplit ) > 1 {
code = authorizeStateSplit [ 0 ]
codeChallenge = authorizeStateSplit [ 1 ]
} else {
nonce = authorizeState
}
go memorystore . Provider . RemoveState ( stateValue )
}
}
if nonce == "" {
nonce = uuid . New ( ) . String ( )
}
authToken , err := token . CreateAuthToken ( ctx , user , inputRoles , scopes , provider , nonce , code )
2022-03-07 08:31:39 +05:30
if err != nil {
2022-05-23 11:52:51 +05:30
log . Debug ( "Failed to create auth token: " , err )
2022-07-10 21:49:33 +05:30
ctx . JSON ( 500 , gin . H { "error" : err . Error ( ) } )
2022-03-07 08:31:39 +05:30
}
2022-03-25 15:21:20 +03:00
2022-11-15 21:45:08 +05:30
// Code challenge could be optional if PKCE flow is not used
if code != "" {
if err := memorystore . Provider . SetState ( code , codeChallenge + "@@" + authToken . FingerPrintHash ) ; err != nil {
log . Debug ( "SetState failed: " , err )
ctx . JSON ( 500 , gin . H { "error" : err . Error ( ) } )
}
}
2022-03-25 15:21:20 +03:00
expiresIn := authToken . AccessToken . ExpiresAt - time . Now ( ) . Unix ( )
if expiresIn <= 0 {
expiresIn = 1
}
2022-11-15 21:45:08 +05:30
params := "access_token=" + authToken . AccessToken . Token + "&token_type=bearer&expires_in=" + strconv . FormatInt ( expiresIn , 10 ) + "&state=" + stateValue + "&id_token=" + authToken . IDToken . Token + "&nonce=" + nonce
if code != "" {
params += "&code=" + code
}
2022-03-08 12:36:26 +05:30
2022-06-29 22:24:00 +05:30
sessionKey := provider + ":" + user . ID
2022-07-10 21:49:33 +05:30
cookie . SetSession ( ctx , authToken . FingerPrintHash )
2023-04-08 13:06:15 +05:30
memorystore . Provider . SetUserSession ( sessionKey , constants . TokenTypeSessionToken + "_" + authToken . FingerPrint , authToken . FingerPrintHash , authToken . SessionTokenExpiresAt )
memorystore . Provider . SetUserSession ( sessionKey , constants . TokenTypeAccessToken + "_" + authToken . FingerPrint , authToken . AccessToken . Token , authToken . AccessToken . ExpiresAt )
2022-03-08 12:36:26 +05:30
if authToken . RefreshToken != nil {
2022-11-15 21:45:08 +05:30
params += ` &refresh_token= ` + authToken . RefreshToken . Token
2023-04-08 13:06:15 +05:30
memorystore . Provider . SetUserSession ( sessionKey , constants . TokenTypeRefreshToken + "_" + authToken . FingerPrint , authToken . RefreshToken . Token , authToken . RefreshToken . ExpiresAt )
2022-03-08 12:36:26 +05:30
}
2022-01-17 11:32:13 +05:30
2022-07-11 10:42:42 +05:30
go func ( ) {
if isSignUp {
2022-07-11 19:40:54 +05:30
utils . RegisterEvent ( ctx , constants . UserSignUpWebhookEvent , provider , user )
2023-08-02 10:02:41 +05:30
// User is also logged in with signup
utils . RegisterEvent ( ctx , constants . UserLoginWebhookEvent , provider , user )
2022-07-11 10:42:42 +05:30
} else {
utils . RegisterEvent ( ctx , constants . UserLoginWebhookEvent , provider , user )
}
2023-07-31 16:42:11 +05:30
db . Provider . AddSession ( ctx , & models . Session {
2022-07-11 10:42:42 +05:30
UserID : user . ID ,
UserAgent : utils . GetUserAgent ( ctx . Request ) ,
IP : utils . GetIP ( ctx . Request ) ,
} )
} ( )
2022-03-08 12:36:26 +05:30
if strings . Contains ( redirectURL , "?" ) {
redirectURL = redirectURL + "&" + params
} else {
2022-06-05 22:46:56 +05:30
redirectURL = redirectURL + "?" + strings . TrimPrefix ( params , "&" )
2022-03-08 12:36:26 +05:30
}
2022-07-10 21:49:33 +05:30
ctx . Redirect ( http . StatusFound , redirectURL )
2022-01-17 11:32:13 +05:30
}
}
2023-08-17 14:20:31 +05:30
func processGoogleUserInfo ( ctx context . Context , code string ) ( * models . User , error ) {
2021-12-03 22:55:27 +05:30
oauth2Token , err := oauth . OAuthProviders . GoogleConfig . Exchange ( ctx , code )
2021-07-17 21:59:50 +05:30
if err != nil {
2022-05-23 11:52:51 +05:30
log . Debug ( "Failed to exchange code for token: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "invalid google exchange code: %s" , err . Error ( ) )
2021-07-17 21:59:50 +05:30
}
2021-12-03 22:55:27 +05:30
verifier := oauth . OIDCProviders . GoogleOIDC . Verifier ( & oidc . Config { ClientID : oauth . OAuthProviders . GoogleConfig . ClientID } )
// Extract the ID Token from OAuth2 token.
rawIDToken , ok := oauth2Token . Extra ( "id_token" ) . ( string )
if ! ok {
2022-05-25 12:30:22 +05:30
log . Debug ( "Failed to extract ID Token from OAuth2 token" )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "unable to extract id_token" )
2021-07-17 21:59:50 +05:30
}
2021-12-03 22:55:27 +05:30
// Parse and verify ID Token payload.
idToken , err := verifier . Verify ( ctx , rawIDToken )
2021-07-17 21:59:50 +05:30
if err != nil {
2022-05-23 11:52:51 +05:30
log . Debug ( "Failed to verify ID Token: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "unable to verify id_token: %s" , err . Error ( ) )
2021-07-17 21:59:50 +05:30
}
2023-11-09 13:45:29 +05:30
user := & models . User { }
2021-12-22 10:51:12 +05:30
if err := idToken . Claims ( & user ) ; err != nil {
2022-05-23 11:52:51 +05:30
log . Debug ( "Failed to parse ID Token claims: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "unable to extract claims" )
2021-12-03 22:55:27 +05:30
}
2021-07-17 21:59:50 +05:30
2021-10-13 22:11:41 +05:30
return user , nil
2021-07-18 04:48:42 +05:30
}
2021-07-17 21:59:50 +05:30
2023-08-17 14:20:31 +05:30
func processGithubUserInfo ( ctx context . Context , code string ) ( * models . User , error ) {
oauth2Token , err := oauth . OAuthProviders . GithubConfig . Exchange ( ctx , code )
2021-07-18 04:48:42 +05:30
if err != nil {
2022-05-25 12:30:22 +05:30
log . Debug ( "Failed to exchange code for token: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "invalid github exchange code: %s" , err . Error ( ) )
2021-07-18 04:48:42 +05:30
}
client := http . Client { }
req , err := http . NewRequest ( "GET" , constants . GithubUserInfoURL , nil )
if err != nil {
2022-05-25 12:30:22 +05:30
log . Debug ( "Failed to create github user info request: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "error creating github user info request: %s" , err . Error ( ) )
2021-07-18 04:48:42 +05:30
}
2022-09-14 11:45:38 +05:30
req . Header . Set (
"Authorization" , fmt . Sprintf ( "token %s" , oauth2Token . AccessToken ) ,
)
2021-07-17 21:59:50 +05:30
2021-07-18 04:48:42 +05:30
response , err := client . Do ( req )
if err != nil {
2022-05-25 12:30:22 +05:30
log . Debug ( "Failed to request github user info: " , err )
2023-11-09 13:45:29 +05:30
return nil , err
2021-07-18 04:48:42 +05:30
}
defer response . Body . Close ( )
2022-12-23 18:19:44 +01:00
body , err := io . ReadAll ( response . Body )
2021-07-18 04:48:42 +05:30
if err != nil {
2022-05-25 12:30:22 +05:30
log . Debug ( "Failed to read github user info response body: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "failed to read github response body: %s" , err . Error ( ) )
2021-07-18 04:48:42 +05:30
}
2022-06-06 22:08:32 +05:30
if response . StatusCode >= 400 {
2022-06-12 18:30:33 +05:30
log . Debug ( "Failed to request github user info: " , string ( body ) )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "failed to request github user info: %s" , string ( body ) )
2022-06-06 22:08:32 +05:30
}
2021-07-18 04:48:42 +05:30
userRawData := make ( map [ string ] string )
json . Unmarshal ( body , & userRawData )
name := strings . Split ( userRawData [ "name" ] , " " )
firstName := ""
lastName := ""
if len ( name ) >= 1 && strings . TrimSpace ( name [ 0 ] ) != "" {
firstName = name [ 0 ]
}
if len ( name ) > 1 && strings . TrimSpace ( name [ 1 ] ) != "" {
lastName = name [ 0 ]
}
2021-12-22 10:51:12 +05:30
2021-12-22 15:31:45 +05:30
picture := userRawData [ "avatar_url" ]
2022-07-17 12:07:17 +05:30
email := userRawData [ "email" ]
if email == "" {
type GithubUserEmails struct {
Email string ` json:"email" `
Primary bool ` json:"primary" `
}
// fetch using /users/email endpoint
2022-09-14 11:45:38 +05:30
req , err := http . NewRequest ( http . MethodGet , constants . GithubUserEmails , nil )
2022-07-17 12:07:17 +05:30
if err != nil {
log . Debug ( "Failed to create github emails request: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "error creating github user info request: %s" , err . Error ( ) )
2022-07-17 12:07:17 +05:30
}
2022-09-14 11:45:38 +05:30
req . Header . Set (
"Authorization" , fmt . Sprintf ( "token %s" , oauth2Token . AccessToken ) ,
)
2022-07-17 12:07:17 +05:30
response , err := client . Do ( req )
if err != nil {
log . Debug ( "Failed to request github user email: " , err )
2023-11-09 13:45:29 +05:30
return nil , err
2022-07-17 12:07:17 +05:30
}
defer response . Body . Close ( )
2022-12-23 18:19:44 +01:00
body , err := io . ReadAll ( response . Body )
2022-07-17 12:07:17 +05:30
if err != nil {
log . Debug ( "Failed to read github user email response body: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "failed to read github response body: %s" , err . Error ( ) )
2022-07-17 12:07:17 +05:30
}
if response . StatusCode >= 400 {
log . Debug ( "Failed to request github user email: " , string ( body ) )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "failed to request github user info: %s" , string ( body ) )
2022-07-17 12:07:17 +05:30
}
emailData := [ ] GithubUserEmails { }
err = json . Unmarshal ( body , & emailData )
if err != nil {
log . Debug ( "Failed to parse github user email: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "failed to parse github user email: %s" , err . Error ( ) )
2022-07-17 12:07:17 +05:30
}
for _ , userEmail := range emailData {
email = userEmail . Email
if userEmail . Primary {
break
}
}
}
2021-12-22 15:31:45 +05:30
2023-11-09 13:45:29 +05:30
user := & models . User {
2021-12-22 15:31:45 +05:30
GivenName : & firstName ,
FamilyName : & lastName ,
Picture : & picture ,
2023-10-26 00:55:10 +05:30
Email : & email ,
2021-07-18 04:48:42 +05:30
}
2021-10-13 22:11:41 +05:30
return user , nil
2021-07-17 21:59:50 +05:30
}
2023-08-17 14:20:31 +05:30
func processFacebookUserInfo ( ctx context . Context , code string ) ( * models . User , error ) {
oauth2Token , err := oauth . OAuthProviders . FacebookConfig . Exchange ( ctx , code )
2021-09-05 03:57:29 +05:30
if err != nil {
2022-05-25 12:30:22 +05:30
log . Debug ( "Invalid facebook exchange code: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "invalid facebook exchange code: %s" , err . Error ( ) )
2021-09-05 03:57:29 +05:30
}
client := http . Client { }
2022-06-12 18:30:33 +05:30
req , err := http . NewRequest ( "GET" , constants . FacebookUserInfoURL + oauth2Token . AccessToken , nil )
2021-09-05 03:57:29 +05:30
if err != nil {
2022-05-25 12:30:22 +05:30
log . Debug ( "Error creating facebook user info request: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "error creating facebook user info request: %s" , err . Error ( ) )
2021-09-05 03:57:29 +05:30
}
response , err := client . Do ( req )
if err != nil {
2022-05-25 12:30:22 +05:30
log . Debug ( "Failed to process facebook user: " , err )
2023-11-09 13:45:29 +05:30
return nil , err
2021-09-05 03:57:29 +05:30
}
defer response . Body . Close ( )
2022-12-23 18:19:44 +01:00
body , err := io . ReadAll ( response . Body )
2021-09-05 03:57:29 +05:30
if err != nil {
2022-05-25 12:30:22 +05:30
log . Debug ( "Failed to read facebook response: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "failed to read facebook response body: %s" , err . Error ( ) )
2021-09-05 03:57:29 +05:30
}
2022-06-06 22:08:32 +05:30
if response . StatusCode >= 400 {
2022-06-12 18:30:33 +05:30
log . Debug ( "Failed to request facebook user info: " , string ( body ) )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "failed to request facebook user info: %s" , string ( body ) )
2022-06-06 22:08:32 +05:30
}
2021-09-05 03:57:29 +05:30
userRawData := make ( map [ string ] interface { } )
json . Unmarshal ( body , & userRawData )
2023-05-28 17:10:29 +03:00
email := fmt . Sprintf ( "%v" , userRawData [ "email" ] )
2021-09-05 03:57:29 +05:30
picObject := userRawData [ "picture" ] . ( map [ string ] interface { } ) [ "data" ]
picDataObject := picObject . ( map [ string ] interface { } )
2021-12-22 15:31:45 +05:30
firstName := fmt . Sprintf ( "%v" , userRawData [ "first_name" ] )
lastName := fmt . Sprintf ( "%v" , userRawData [ "last_name" ] )
picture := fmt . Sprintf ( "%v" , picDataObject [ "url" ] )
2023-11-09 13:45:29 +05:30
user := & models . User {
2021-12-22 15:31:45 +05:30
GivenName : & firstName ,
FamilyName : & lastName ,
Picture : & picture ,
2023-10-26 00:55:10 +05:30
Email : & email ,
2021-09-05 03:57:29 +05:30
}
2021-10-13 22:11:41 +05:30
return user , nil
2021-09-05 03:57:29 +05:30
}
2022-06-06 22:08:32 +05:30
2023-08-17 14:20:31 +05:30
func processLinkedInUserInfo ( ctx context . Context , code string ) ( * models . User , error ) {
oauth2Token , err := oauth . OAuthProviders . LinkedInConfig . Exchange ( ctx , code )
2022-06-06 22:08:32 +05:30
if err != nil {
log . Debug ( "Failed to exchange code for token: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "invalid linkedin exchange code: %s" , err . Error ( ) )
2022-06-06 22:08:32 +05:30
}
client := http . Client { }
req , err := http . NewRequest ( "GET" , constants . LinkedInUserInfoURL , nil )
if err != nil {
log . Debug ( "Failed to create linkedin user info request: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "error creating linkedin user info request: %s" , err . Error ( ) )
2022-06-06 22:08:32 +05:30
}
req . Header = http . Header {
2022-06-12 18:30:33 +05:30
"Authorization" : [ ] string { fmt . Sprintf ( "Bearer %s" , oauth2Token . AccessToken ) } ,
2022-06-06 22:08:32 +05:30
}
response , err := client . Do ( req )
if err != nil {
log . Debug ( "Failed to request linkedin user info: " , err )
2023-11-09 13:45:29 +05:30
return nil , err
2022-06-06 22:08:32 +05:30
}
defer response . Body . Close ( )
2022-12-23 18:19:44 +01:00
body , err := io . ReadAll ( response . Body )
2022-06-06 22:08:32 +05:30
if err != nil {
log . Debug ( "Failed to read linkedin user info response body: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "failed to read linkedin response body: %s" , err . Error ( ) )
2022-06-06 22:08:32 +05:30
}
if response . StatusCode >= 400 {
log . Debug ( "Failed to request linkedin user info: " , string ( body ) )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "failed to request linkedin user info: %s" , string ( body ) )
2022-06-06 22:08:32 +05:30
}
userRawData := make ( map [ string ] interface { } )
json . Unmarshal ( body , & userRawData )
req , err = http . NewRequest ( "GET" , constants . LinkedInEmailURL , nil )
if err != nil {
log . Debug ( "Failed to create linkedin email info request: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "error creating linkedin user info request: %s" , err . Error ( ) )
2022-06-06 22:08:32 +05:30
}
req . Header = http . Header {
2022-06-12 18:30:33 +05:30
"Authorization" : [ ] string { fmt . Sprintf ( "Bearer %s" , oauth2Token . AccessToken ) } ,
2022-06-06 22:08:32 +05:30
}
response , err = client . Do ( req )
if err != nil {
log . Debug ( "Failed to request linkedin email info: " , err )
2023-11-09 13:45:29 +05:30
return nil , err
2022-06-06 22:08:32 +05:30
}
defer response . Body . Close ( )
2022-12-23 18:19:44 +01:00
body , err = io . ReadAll ( response . Body )
2022-06-06 22:08:32 +05:30
if err != nil {
log . Debug ( "Failed to read linkedin email info response body: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "failed to read linkedin email response body: %s" , err . Error ( ) )
2022-06-06 22:08:32 +05:30
}
if response . StatusCode >= 400 {
log . Debug ( "Failed to request linkedin user info: " , string ( body ) )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "failed to request linkedin user info: %s" , string ( body ) )
2022-06-06 22:08:32 +05:30
}
emailRawData := make ( map [ string ] interface { } )
json . Unmarshal ( body , & emailRawData )
firstName := userRawData [ "localizedFirstName" ] . ( string )
lastName := userRawData [ "localizedLastName" ] . ( string )
profilePicture := userRawData [ "profilePicture" ] . ( map [ string ] interface { } ) [ "displayImage~" ] . ( map [ string ] interface { } ) [ "elements" ] . ( [ ] interface { } ) [ 0 ] . ( map [ string ] interface { } ) [ "identifiers" ] . ( [ ] interface { } ) [ 0 ] . ( map [ string ] interface { } ) [ "identifier" ] . ( string )
emailAddress := emailRawData [ "elements" ] . ( [ ] interface { } ) [ 0 ] . ( map [ string ] interface { } ) [ "handle~" ] . ( map [ string ] interface { } ) [ "emailAddress" ] . ( string )
2023-11-09 13:45:29 +05:30
user := & models . User {
2022-06-06 22:08:32 +05:30
GivenName : & firstName ,
FamilyName : & lastName ,
Picture : & profilePicture ,
2023-10-26 00:55:10 +05:30
Email : & emailAddress ,
2022-06-06 22:08:32 +05:30
}
return user , nil
}
2022-06-12 18:30:33 +05:30
2023-08-17 14:20:31 +05:30
func processAppleUserInfo ( ctx context . Context , code string ) ( * models . User , error ) {
2023-11-09 13:45:29 +05:30
var user = & models . User { }
2023-08-17 14:20:31 +05:30
oauth2Token , err := oauth . OAuthProviders . AppleConfig . Exchange ( ctx , code )
2022-06-12 18:30:33 +05:30
if err != nil {
log . Debug ( "Failed to exchange code for token: " , err )
return user , fmt . Errorf ( "invalid apple exchange code: %s" , err . Error ( ) )
}
// Extract the ID Token from OAuth2 token.
rawIDToken , ok := oauth2Token . Extra ( "id_token" ) . ( string )
if ! ok {
log . Debug ( "Failed to extract ID Token from OAuth2 token" )
return user , fmt . Errorf ( "unable to extract id_token" )
}
2022-06-14 11:38:04 +05:30
tokenSplit := strings . Split ( rawIDToken , "." )
claimsData := tokenSplit [ 1 ]
2022-06-15 21:55:41 +05:30
decodedClaimsData , err := base64 . RawURLEncoding . DecodeString ( claimsData )
2022-06-12 18:30:33 +05:30
if err != nil {
2022-06-15 21:55:41 +05:30
log . Debugf ( "Failed to decrypt claims %s: %s" , claimsData , err . Error ( ) )
2022-06-14 11:38:04 +05:30
return user , fmt . Errorf ( "failed to decrypt claims data: %s" , err . Error ( ) )
}
2022-06-14 12:35:23 +05:30
claims := make ( map [ string ] interface { } )
2022-06-15 21:55:41 +05:30
err = json . Unmarshal ( decodedClaimsData , & claims )
2022-06-14 11:38:04 +05:30
if err != nil {
log . Debug ( "Failed to unmarshal claims data: " , err )
return user , fmt . Errorf ( "failed to unmarshal claims data: %s" , err . Error ( ) )
2022-06-12 18:30:33 +05:30
}
2023-11-09 13:45:29 +05:30
if val , ok := claims [ "email" ] ; ! ok || val == nil {
2022-06-14 13:37:05 +05:30
log . Debug ( "Failed to extract email from claims." )
return user , fmt . Errorf ( "unable to extract email, please check the scopes enabled for your app. It needs `email`, `name` scopes" )
2022-06-14 12:35:23 +05:30
} else {
2023-10-26 00:55:10 +05:30
email := val . ( string )
user . Email = & email
2022-06-14 12:35:23 +05:30
}
if val , ok := claims [ "name" ] ; ok {
2022-06-14 13:11:39 +05:30
nameData := val . ( map [ string ] interface { } )
2022-06-14 15:45:06 +05:30
if nameVal , ok := nameData [ "firstName" ] ; ok {
givenName := nameVal . ( string )
user . GivenName = & givenName
}
if nameVal , ok := nameData [ "lastName" ] ; ok {
familyName := nameVal . ( string )
user . FamilyName = & familyName
}
2022-06-14 12:35:23 +05:30
}
2022-06-12 18:30:33 +05:30
return user , err
}
2022-08-13 12:35:00 +05:30
2023-08-17 14:20:31 +05:30
func processTwitterUserInfo ( ctx context . Context , code , verifier string ) ( * models . User , error ) {
oauth2Token , err := oauth . OAuthProviders . TwitterConfig . Exchange ( ctx , code , oauth2 . SetAuthURLParam ( "code_verifier" , verifier ) )
2022-08-14 20:19:48 +02:00
if err != nil {
log . Debug ( "Failed to exchange code for token: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "invalid twitter exchange code: %s" , err . Error ( ) )
2022-08-14 20:19:48 +02:00
}
client := http . Client { }
req , err := http . NewRequest ( "GET" , constants . TwitterUserInfoURL , nil )
if err != nil {
log . Debug ( "Failed to create Twitter user info request: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "error creating Twitter user info request: %s" , err . Error ( ) )
2022-08-14 20:19:48 +02:00
}
req . Header = http . Header {
"Authorization" : [ ] string { fmt . Sprintf ( "Bearer %s" , oauth2Token . AccessToken ) } ,
}
response , err := client . Do ( req )
if err != nil {
log . Debug ( "Failed to request Twitter user info: " , err )
2023-11-09 13:45:29 +05:30
return nil , err
2022-08-14 20:19:48 +02:00
}
defer response . Body . Close ( )
2022-12-23 18:19:44 +01:00
body , err := io . ReadAll ( response . Body )
2022-08-14 20:19:48 +02:00
if err != nil {
log . Debug ( "Failed to read Twitter user info response body: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "failed to read Twitter response body: %s" , err . Error ( ) )
2022-08-14 20:19:48 +02:00
}
if response . StatusCode >= 400 {
log . Debug ( "Failed to request Twitter user info: " , string ( body ) )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "failed to request Twitter user info: %s" , string ( body ) )
2022-08-14 20:19:48 +02:00
}
responseRawData := make ( map [ string ] interface { } )
json . Unmarshal ( body , & responseRawData )
userRawData := responseRawData [ "data" ] . ( map [ string ] interface { } )
2022-08-29 08:19:11 +05:30
// log.Info(userRawData)
2022-08-14 20:19:48 +02:00
// Twitter API does not return E-Mail adresses by default. For that case special privileges have
// to be granted on a per-App basis. See https://developer.twitter.com/en/docs/twitter-api/v1/accounts-and-users/manage-account-settings/api-reference/get-account-verify_credentials
// Currently Twitter API only provides the full name of a user. To fill givenName and familyName
// the full name will be split at the first whitespace. This approach will not be valid for all name combinations
nameArr := strings . SplitAfterN ( userRawData [ "name" ] . ( string ) , " " , 2 )
firstName := nameArr [ 0 ]
lastName := ""
if len ( nameArr ) == 2 {
lastName = nameArr [ 1 ]
}
nickname := userRawData [ "username" ] . ( string )
profilePicture := userRawData [ "profile_image_url" ] . ( string )
2023-11-09 13:45:29 +05:30
user := & models . User {
2022-08-14 20:19:48 +02:00
GivenName : & firstName ,
FamilyName : & lastName ,
Picture : & profilePicture ,
Nickname : & nickname ,
}
2022-08-13 12:35:00 +05:30
return user , nil
}
2023-02-26 05:23:02 +05:30
// process microsoft user information
2023-08-17 14:20:31 +05:30
func processMicrosoftUserInfo ( ctx context . Context , code string ) ( * models . User , error ) {
2023-02-26 05:23:02 +05:30
oauth2Token , err := oauth . OAuthProviders . MicrosoftConfig . Exchange ( ctx , code )
if err != nil {
log . Debug ( "Failed to exchange code for token: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "invalid microsoft exchange code: %s" , err . Error ( ) )
2023-02-26 05:23:02 +05:30
}
2023-08-17 14:20:31 +05:30
// we need to skip issuer check because for common tenant it will return internal issuer which does not match
verifier := oauth . OIDCProviders . MicrosoftOIDC . Verifier ( & oidc . Config {
ClientID : oauth . OAuthProviders . MicrosoftConfig . ClientID ,
SkipIssuerCheck : true ,
} )
2023-02-26 05:23:02 +05:30
// Extract the ID Token from OAuth2 token.
rawIDToken , ok := oauth2Token . Extra ( "id_token" ) . ( string )
if ! ok {
log . Debug ( "Failed to extract ID Token from OAuth2 token" )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "unable to extract id_token" )
2023-02-26 05:23:02 +05:30
}
// Parse and verify ID Token payload.
idToken , err := verifier . Verify ( ctx , rawIDToken )
if err != nil {
log . Debug ( "Failed to verify ID Token: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "unable to verify id_token: %s" , err . Error ( ) )
2023-02-26 05:23:02 +05:30
}
2023-11-09 13:45:29 +05:30
user := & models . User { }
2023-02-26 05:23:02 +05:30
if err := idToken . Claims ( & user ) ; err != nil {
log . Debug ( "Failed to parse ID Token claims: " , err )
2023-11-09 13:45:29 +05:30
return nil , fmt . Errorf ( "unable to extract claims" )
2023-02-26 05:23:02 +05:30
}
return user , nil
}
2023-12-02 12:21:53 +05:30
// process twitch user information
func processTwitchUserInfo ( ctx context . Context , code string ) ( * models . User , error ) {
oauth2Token , err := oauth . OAuthProviders . TwitchConfig . Exchange ( ctx , code )
if err != nil {
log . Debug ( "Failed to exchange code for token: " , err )
return nil , fmt . Errorf ( "invalid twitch exchange code: %s" , err . Error ( ) )
}
// Extract the ID Token from OAuth2 token.
rawIDToken , ok := oauth2Token . Extra ( "id_token" ) . ( string )
if ! ok {
log . Debug ( "Failed to extract ID Token from OAuth2 token" )
return nil , fmt . Errorf ( "unable to extract id_token" )
}
verifier := oauth . OIDCProviders . TwitchOIDC . Verifier ( & oidc . Config {
ClientID : oauth . OAuthProviders . TwitchConfig . ClientID ,
SkipIssuerCheck : true ,
} )
// Parse and verify ID Token payload.
idToken , err := verifier . Verify ( ctx , rawIDToken )
if err != nil {
log . Debug ( "Failed to verify ID Token: " , err )
return nil , fmt . Errorf ( "unable to verify id_token: %s" , err . Error ( ) )
}
user := & models . User { }
if err := idToken . Claims ( & user ) ; err != nil {
log . Debug ( "Failed to parse ID Token claims: " , err )
return nil , fmt . Errorf ( "unable to extract claims" )
}
return user , nil
}