2021-07-17 16:29:50 +00:00
package handlers
import (
2021-12-03 17:25:27 +00:00
"context"
2022-06-15 16:25:41 +00:00
"encoding/base64"
2021-07-17 16:29:50 +00:00
"encoding/json"
"fmt"
2022-12-23 17:19:44 +00:00
"io"
2021-07-17 16:29:50 +00:00
"net/http"
"strings"
"time"
2023-12-02 06:51:53 +00:00
"golang.org/x/oauth2"
2022-05-23 06:22:51 +00:00
"github.com/coreos/go-oidc/v3/oidc"
"github.com/gin-gonic/gin"
2022-10-23 15:38:08 +00:00
"github.com/google/uuid"
2023-12-02 06:51:53 +00:00
2022-05-23 06:22:51 +00:00
log "github.com/sirupsen/logrus"
2021-07-23 16:27:44 +00:00
"github.com/authorizerdev/authorizer/server/constants"
2022-01-22 19:54:41 +00:00
"github.com/authorizerdev/authorizer/server/cookie"
2021-07-23 16:27:44 +00:00
"github.com/authorizerdev/authorizer/server/db"
2022-01-21 08:04:04 +00:00
"github.com/authorizerdev/authorizer/server/db/models"
2022-05-27 17:50:38 +00:00
"github.com/authorizerdev/authorizer/server/memorystore"
2021-07-23 16:27:44 +00:00
"github.com/authorizerdev/authorizer/server/oauth"
2023-10-25 19:25:10 +00:00
"github.com/authorizerdev/authorizer/server/refs"
2022-01-22 19:54:41 +00:00
"github.com/authorizerdev/authorizer/server/token"
2021-07-23 16:27:44 +00:00
"github.com/authorizerdev/authorizer/server/utils"
2021-07-17 16:29:50 +00:00
)
2022-01-17 06:02:13 +00:00
// OAuthCallbackHandler handles the OAuth callback for various oauth providers
func OAuthCallbackHandler ( ) gin . HandlerFunc {
2022-07-10 16:19:33 +00:00
return func ( ctx * gin . Context ) {
provider := ctx . Param ( "oauth_provider" )
state := ctx . Request . FormValue ( "state" )
2022-05-29 11:52:46 +00:00
sessionState , err := memorystore . Provider . GetState ( state )
if sessionState == "" || err != nil {
2022-05-25 07:00:22 +00:00
log . Debug ( "Invalid oauth state: " , state )
2022-07-10 16:19:33 +00:00
ctx . JSON ( 400 , gin . H { "error" : "invalid oauth state" } )
2023-08-17 08:50:31 +00:00
return
2022-01-17 06:02:13 +00:00
}
// contains random token, redirect url, role
2022-03-08 13:43:45 +00:00
sessionSplit := strings . Split ( state , "___" )
2022-01-17 06:02:13 +00:00
2022-03-08 07:06:26 +00:00
if len ( sessionSplit ) < 3 {
2022-05-25 07:00:22 +00:00
log . Debug ( "Unable to get redirect url from state: " , state )
2022-07-10 16:19:33 +00:00
ctx . JSON ( 400 , gin . H { "error" : "invalid redirect url" } )
2022-01-17 06:02:13 +00:00
return
}
2022-06-11 18:57:21 +00:00
// remove state from store
go memorystore . Provider . RemoveState ( state )
2022-03-08 07:06:26 +00:00
stateValue := sessionSplit [ 0 ]
2022-01-17 06:02:13 +00:00
redirectURL := sessionSplit [ 1 ]
2022-03-08 07:06:26 +00:00
inputRoles := strings . Split ( sessionSplit [ 2 ] , "," )
scopes := strings . Split ( sessionSplit [ 3 ] , "," )
2023-07-31 11:12:11 +00:00
var user * models . User
2022-11-15 16:15:08 +00:00
oauthCode := ctx . Request . FormValue ( "code" )
2023-08-17 08:50:31 +00:00
if oauthCode == "" {
log . Debug ( "Invalid oauth code: " , oauthCode )
ctx . JSON ( 400 , gin . H { "error" : "invalid oauth code" } )
return
}
2022-01-17 06:02:13 +00:00
switch provider {
2022-06-29 16:54:00 +00:00
case constants . AuthRecipeMethodGoogle :
2023-08-17 08:50:31 +00:00
user , err = processGoogleUserInfo ( ctx , oauthCode )
2022-06-29 16:54:00 +00:00
case constants . AuthRecipeMethodGithub :
2023-08-17 08:50:31 +00:00
user , err = processGithubUserInfo ( ctx , oauthCode )
2022-06-29 16:54:00 +00:00
case constants . AuthRecipeMethodFacebook :
2023-08-17 08:50:31 +00:00
user , err = processFacebookUserInfo ( ctx , oauthCode )
2022-06-29 16:54:00 +00:00
case constants . AuthRecipeMethodLinkedIn :
2023-08-17 08:50:31 +00:00
user , err = processLinkedInUserInfo ( ctx , oauthCode )
2022-06-29 16:54:00 +00:00
case constants . AuthRecipeMethodApple :
2023-08-17 08:50:31 +00:00
user , err = processAppleUserInfo ( ctx , oauthCode )
2022-08-13 07:05:00 +00:00
case constants . AuthRecipeMethodTwitter :
2023-08-17 08:50:31 +00:00
user , err = processTwitterUserInfo ( ctx , oauthCode , sessionState )
2023-02-25 23:53:02 +00:00
case constants . AuthRecipeMethodMicrosoft :
2023-08-17 08:50:31 +00:00
user , err = processMicrosoftUserInfo ( ctx , oauthCode )
2023-12-02 06:51:53 +00:00
case constants . AuthRecipeMethodTwitch :
user , err = processTwitchUserInfo ( ctx , oauthCode )
2022-01-17 06:02:13 +00:00
default :
2022-05-23 06:22:51 +00:00
log . Info ( "Invalid oauth provider" )
2022-01-17 06:02:13 +00:00
err = fmt . Errorf ( ` invalid oauth provider ` )
}
if err != nil {
2022-05-23 06:22:51 +00:00
log . Debug ( "Failed to process user info: " , err )
2022-07-10 16:19:33 +00:00
ctx . JSON ( 400 , gin . H { "error" : err . Error ( ) } )
2022-01-17 06:02:13 +00:00
return
}
2023-10-25 19:25:10 +00:00
existingUser , err := db . Provider . GetUserByEmail ( ctx , refs . StringValue ( user . Email ) )
2022-05-23 06:22:51 +00:00
log := log . WithField ( "user" , user . Email )
2022-07-11 05:12:42 +00:00
isSignUp := false
2022-01-17 06:02:13 +00:00
if err != nil {
2022-05-29 11:52:46 +00:00
isSignupDisabled , err := memorystore . Provider . GetBoolStoreEnvVariable ( constants . EnvKeyDisableSignUp )
if err != nil {
log . Debug ( "Failed to get signup disabled env variable: " , err )
2022-07-10 16:19:33 +00:00
ctx . JSON ( 400 , gin . H { "error" : err . Error ( ) } )
2022-05-29 11:52:46 +00:00
return
}
if isSignupDisabled {
2022-05-23 06:22:51 +00:00
log . Debug ( "Failed to signup as disabled" )
2022-07-10 16:19:33 +00:00
ctx . JSON ( 400 , gin . H { "error" : "signup is disabled for this instance" } )
2022-03-16 17:19:18 +00:00
return
}
2022-01-17 06:02:13 +00:00
// 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 02:44:03 +00:00
protectedRolesString , err := memorystore . Provider . GetStringStoreEnvVariable ( constants . EnvKeyProtectedRoles )
protectedRoles := [ ] string { }
2022-05-29 11:52:46 +00:00
if err != nil {
log . Debug ( "Failed to get protected roles: " , err )
2022-05-31 02:44:03 +00:00
protectedRolesString = ""
} else {
protectedRoles = strings . Split ( protectedRolesString , "," )
2022-05-29 11:52:46 +00:00
}
if utils . StringSliceContains ( protectedRoles , ir ) {
2022-01-17 06:02:13 +00:00
hasProtectedRole = true
}
}
if hasProtectedRole {
2022-05-25 07:00:22 +00:00
log . Debug ( "Signup is not allowed with protected roles:" , inputRoles )
2022-07-10 16:19:33 +00:00
ctx . JSON ( 400 , gin . H { "error" : "invalid role" } )
2022-01-17 06:02:13 +00:00
return
}
user . Roles = strings . Join ( inputRoles , "," )
now := time . Now ( ) . Unix ( )
user . EmailVerifiedAt = & now
2022-07-10 16:19:33 +00:00
user , _ = db . Provider . AddUser ( ctx , user )
2022-07-11 05:12:42 +00:00
isSignUp = true
2022-01-17 06:02:13 +00:00
} else {
2022-06-11 18:57:21 +00:00
user = existingUser
2022-03-24 08:43:55 +00:00
if user . RevokedTimestamp != nil {
2022-05-25 07:00:22 +00:00
log . Debug ( "User access revoked at: " , user . RevokedTimestamp )
2022-07-10 16:19:33 +00:00
ctx . JSON ( 400 , gin . H { "error" : "user access has been revoked" } )
2022-06-11 18:57:21 +00:00
return
2022-03-24 08:43:55 +00:00
}
2022-01-17 06:02:13 +00:00
// 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 06:05:24 +00:00
if user . EmailVerifiedAt == nil {
now := time . Now ( ) . Unix ( )
user . EmailVerifiedAt = & now
}
2022-01-17 06:02:13 +00:00
// 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 02:44:03 +00:00
protectedRolesString , err := memorystore . Provider . GetStringStoreEnvVariable ( constants . EnvKeyProtectedRoles )
protectedRoles := [ ] string { }
2022-05-29 11:52:46 +00:00
if err != nil {
log . Debug ( "Failed to get protected roles: " , err )
2022-05-31 02:44:03 +00:00
protectedRolesString = ""
} else {
protectedRoles = strings . Split ( protectedRolesString , "," )
2022-05-29 11:52:46 +00:00
}
if utils . StringSliceContains ( protectedRoles , ur ) {
2022-01-17 06:02:13 +00:00
hasProtectedRole = true
}
}
if hasProtectedRole {
2022-05-23 06:22:51 +00:00
log . Debug ( "Invalid role. User is using protected unassigned role" )
2022-07-10 16:19:33 +00:00
ctx . JSON ( 400 , gin . H { "error" : "invalid role" } )
2022-01-17 06:02:13 +00:00
return
} else {
user . Roles = existingUser . Roles + "," + strings . Join ( unasignedRoles , "," )
}
} else {
user . Roles = existingUser . Roles
}
2022-02-02 07:00:11 +00:00
2022-07-10 16:19:33 +00:00
user , err = db . Provider . UpdateUser ( ctx , user )
2022-02-02 07:00:11 +00:00
if err != nil {
2022-05-23 06:22:51 +00:00
log . Debug ( "Failed to update user: " , err )
2022-07-10 16:19:33 +00:00
ctx . JSON ( 500 , gin . H { "error" : err . Error ( ) } )
2022-02-02 07:00:11 +00:00
return
}
2022-01-17 06:02:13 +00:00
}
2022-11-12 19:52:21 +00:00
// TODO
// use stateValue to get code / nonce
// add code / nonce to id_token
2022-11-15 16:15:08 +00:00
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 03:01:39 +00:00
if err != nil {
2022-05-23 06:22:51 +00:00
log . Debug ( "Failed to create auth token: " , err )
2022-07-10 16:19:33 +00:00
ctx . JSON ( 500 , gin . H { "error" : err . Error ( ) } )
2022-03-07 03:01:39 +00:00
}
2022-03-25 12:21:20 +00:00
2022-11-15 16:15:08 +00:00
// 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 12:21:20 +00:00
expiresIn := authToken . AccessToken . ExpiresAt - time . Now ( ) . Unix ( )
if expiresIn <= 0 {
expiresIn = 1
}
2023-12-30 15:49:44 +00:00
// params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + stateValue + "&id_token=" + authToken.IDToken.Token + "&nonce=" + nonce
// Note: If OIDC breaks in the future, use the above params
params := "state=" + stateValue + "&nonce=" + nonce
2022-11-15 16:15:08 +00:00
if code != "" {
params += "&code=" + code
}
2022-03-08 07:06:26 +00:00
2022-06-29 16:54:00 +00:00
sessionKey := provider + ":" + user . ID
2022-07-10 16:19:33 +00:00
cookie . SetSession ( ctx , authToken . FingerPrintHash )
2023-04-08 07:36:15 +00:00
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 07:06:26 +00:00
if authToken . RefreshToken != nil {
2022-11-15 16:15:08 +00:00
params += ` &refresh_token= ` + authToken . RefreshToken . Token
2023-04-08 07:36:15 +00:00
memorystore . Provider . SetUserSession ( sessionKey , constants . TokenTypeRefreshToken + "_" + authToken . FingerPrint , authToken . RefreshToken . Token , authToken . RefreshToken . ExpiresAt )
2022-03-08 07:06:26 +00:00
}
2022-01-17 06:02:13 +00:00
2022-07-11 05:12:42 +00:00
go func ( ) {
if isSignUp {
2022-07-11 14:10:54 +00:00
utils . RegisterEvent ( ctx , constants . UserSignUpWebhookEvent , provider , user )
2023-08-02 04:32:41 +00:00
// User is also logged in with signup
utils . RegisterEvent ( ctx , constants . UserLoginWebhookEvent , provider , user )
2022-07-11 05:12:42 +00:00
} else {
utils . RegisterEvent ( ctx , constants . UserLoginWebhookEvent , provider , user )
}
2023-07-31 11:12:11 +00:00
db . Provider . AddSession ( ctx , & models . Session {
2022-07-11 05:12:42 +00:00
UserID : user . ID ,
UserAgent : utils . GetUserAgent ( ctx . Request ) ,
IP : utils . GetIP ( ctx . Request ) ,
} )
} ( )
2022-03-08 07:06:26 +00:00
if strings . Contains ( redirectURL , "?" ) {
redirectURL = redirectURL + "&" + params
} else {
2022-06-05 17:16:56 +00:00
redirectURL = redirectURL + "?" + strings . TrimPrefix ( params , "&" )
2022-03-08 07:06:26 +00:00
}
2022-07-10 16:19:33 +00:00
ctx . Redirect ( http . StatusFound , redirectURL )
2022-01-17 06:02:13 +00:00
}
}
2023-08-17 08:50:31 +00:00
func processGoogleUserInfo ( ctx context . Context , code string ) ( * models . User , error ) {
2021-12-03 17:25:27 +00:00
oauth2Token , err := oauth . OAuthProviders . GoogleConfig . Exchange ( ctx , code )
2021-07-17 16:29:50 +00:00
if err != nil {
2022-05-23 06:22:51 +00:00
log . Debug ( "Failed to exchange code for token: " , err )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "invalid google exchange code: %s" , err . Error ( ) )
2021-07-17 16:29:50 +00:00
}
2021-12-03 17:25:27 +00:00
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 07:00:22 +00:00
log . Debug ( "Failed to extract ID Token from OAuth2 token" )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "unable to extract id_token" )
2021-07-17 16:29:50 +00:00
}
2021-12-03 17:25:27 +00:00
// Parse and verify ID Token payload.
idToken , err := verifier . Verify ( ctx , rawIDToken )
2021-07-17 16:29:50 +00:00
if err != nil {
2022-05-23 06:22:51 +00:00
log . Debug ( "Failed to verify ID Token: " , err )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "unable to verify id_token: %s" , err . Error ( ) )
2021-07-17 16:29:50 +00:00
}
2023-11-09 08:15:29 +00:00
user := & models . User { }
2021-12-22 05:21:12 +00:00
if err := idToken . Claims ( & user ) ; err != nil {
2022-05-23 06:22:51 +00:00
log . Debug ( "Failed to parse ID Token claims: " , err )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "unable to extract claims" )
2021-12-03 17:25:27 +00:00
}
2021-07-17 16:29:50 +00:00
2021-10-13 16:41:41 +00:00
return user , nil
2021-07-17 23:18:42 +00:00
}
2021-07-17 16:29:50 +00:00
2023-08-17 08:50:31 +00:00
func processGithubUserInfo ( ctx context . Context , code string ) ( * models . User , error ) {
oauth2Token , err := oauth . OAuthProviders . GithubConfig . Exchange ( ctx , code )
2021-07-17 23:18:42 +00:00
if err != nil {
2022-05-25 07:00:22 +00:00
log . Debug ( "Failed to exchange code for token: " , err )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "invalid github exchange code: %s" , err . Error ( ) )
2021-07-17 23:18:42 +00:00
}
client := http . Client { }
req , err := http . NewRequest ( "GET" , constants . GithubUserInfoURL , nil )
if err != nil {
2022-05-25 07:00:22 +00:00
log . Debug ( "Failed to create github user info request: " , err )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "error creating github user info request: %s" , err . Error ( ) )
2021-07-17 23:18:42 +00:00
}
2022-09-14 06:15:38 +00:00
req . Header . Set (
"Authorization" , fmt . Sprintf ( "token %s" , oauth2Token . AccessToken ) ,
)
2021-07-17 16:29:50 +00:00
2021-07-17 23:18:42 +00:00
response , err := client . Do ( req )
if err != nil {
2022-05-25 07:00:22 +00:00
log . Debug ( "Failed to request github user info: " , err )
2023-11-09 08:15:29 +00:00
return nil , err
2021-07-17 23:18:42 +00:00
}
defer response . Body . Close ( )
2022-12-23 17:19:44 +00:00
body , err := io . ReadAll ( response . Body )
2021-07-17 23:18:42 +00:00
if err != nil {
2022-05-25 07:00:22 +00:00
log . Debug ( "Failed to read github user info response body: " , err )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "failed to read github response body: %s" , err . Error ( ) )
2021-07-17 23:18:42 +00:00
}
2022-06-06 16:38:32 +00:00
if response . StatusCode >= 400 {
2022-06-12 13:00:33 +00:00
log . Debug ( "Failed to request github user info: " , string ( body ) )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "failed to request github user info: %s" , string ( body ) )
2022-06-06 16:38:32 +00:00
}
2021-07-17 23:18:42 +00:00
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 05:21:12 +00:00
2021-12-22 10:01:45 +00:00
picture := userRawData [ "avatar_url" ]
2022-07-17 06:37:17 +00:00
email := userRawData [ "email" ]
if email == "" {
type GithubUserEmails struct {
Email string ` json:"email" `
Primary bool ` json:"primary" `
}
// fetch using /users/email endpoint
2022-09-14 06:15:38 +00:00
req , err := http . NewRequest ( http . MethodGet , constants . GithubUserEmails , nil )
2022-07-17 06:37:17 +00:00
if err != nil {
log . Debug ( "Failed to create github emails request: " , err )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "error creating github user info request: %s" , err . Error ( ) )
2022-07-17 06:37:17 +00:00
}
2022-09-14 06:15:38 +00:00
req . Header . Set (
"Authorization" , fmt . Sprintf ( "token %s" , oauth2Token . AccessToken ) ,
)
2022-07-17 06:37:17 +00:00
response , err := client . Do ( req )
if err != nil {
log . Debug ( "Failed to request github user email: " , err )
2023-11-09 08:15:29 +00:00
return nil , err
2022-07-17 06:37:17 +00:00
}
defer response . Body . Close ( )
2022-12-23 17:19:44 +00:00
body , err := io . ReadAll ( response . Body )
2022-07-17 06:37:17 +00:00
if err != nil {
log . Debug ( "Failed to read github user email response body: " , err )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "failed to read github response body: %s" , err . Error ( ) )
2022-07-17 06:37:17 +00:00
}
if response . StatusCode >= 400 {
log . Debug ( "Failed to request github user email: " , string ( body ) )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "failed to request github user info: %s" , string ( body ) )
2022-07-17 06:37:17 +00:00
}
emailData := [ ] GithubUserEmails { }
err = json . Unmarshal ( body , & emailData )
if err != nil {
log . Debug ( "Failed to parse github user email: " , err )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "failed to parse github user email: %s" , err . Error ( ) )
2022-07-17 06:37:17 +00:00
}
for _ , userEmail := range emailData {
email = userEmail . Email
if userEmail . Primary {
break
}
}
}
2021-12-22 10:01:45 +00:00
2023-11-09 08:15:29 +00:00
user := & models . User {
2021-12-22 10:01:45 +00:00
GivenName : & firstName ,
FamilyName : & lastName ,
Picture : & picture ,
2023-10-25 19:25:10 +00:00
Email : & email ,
2021-07-17 23:18:42 +00:00
}
2021-10-13 16:41:41 +00:00
return user , nil
2021-07-17 16:29:50 +00:00
}
2023-08-17 08:50:31 +00:00
func processFacebookUserInfo ( ctx context . Context , code string ) ( * models . User , error ) {
oauth2Token , err := oauth . OAuthProviders . FacebookConfig . Exchange ( ctx , code )
2021-09-04 22:27:29 +00:00
if err != nil {
2022-05-25 07:00:22 +00:00
log . Debug ( "Invalid facebook exchange code: " , err )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "invalid facebook exchange code: %s" , err . Error ( ) )
2021-09-04 22:27:29 +00:00
}
client := http . Client { }
2022-06-12 13:00:33 +00:00
req , err := http . NewRequest ( "GET" , constants . FacebookUserInfoURL + oauth2Token . AccessToken , nil )
2021-09-04 22:27:29 +00:00
if err != nil {
2022-05-25 07:00:22 +00:00
log . Debug ( "Error creating facebook user info request: " , err )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "error creating facebook user info request: %s" , err . Error ( ) )
2021-09-04 22:27:29 +00:00
}
response , err := client . Do ( req )
if err != nil {
2022-05-25 07:00:22 +00:00
log . Debug ( "Failed to process facebook user: " , err )
2023-11-09 08:15:29 +00:00
return nil , err
2021-09-04 22:27:29 +00:00
}
defer response . Body . Close ( )
2022-12-23 17:19:44 +00:00
body , err := io . ReadAll ( response . Body )
2021-09-04 22:27:29 +00:00
if err != nil {
2022-05-25 07:00:22 +00:00
log . Debug ( "Failed to read facebook response: " , err )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "failed to read facebook response body: %s" , err . Error ( ) )
2021-09-04 22:27:29 +00:00
}
2022-06-06 16:38:32 +00:00
if response . StatusCode >= 400 {
2022-06-12 13:00:33 +00:00
log . Debug ( "Failed to request facebook user info: " , string ( body ) )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "failed to request facebook user info: %s" , string ( body ) )
2022-06-06 16:38:32 +00:00
}
2021-09-04 22:27:29 +00:00
userRawData := make ( map [ string ] interface { } )
json . Unmarshal ( body , & userRawData )
2023-05-28 14:10:29 +00:00
email := fmt . Sprintf ( "%v" , userRawData [ "email" ] )
2021-09-04 22:27:29 +00:00
picObject := userRawData [ "picture" ] . ( map [ string ] interface { } ) [ "data" ]
picDataObject := picObject . ( map [ string ] interface { } )
2021-12-22 10:01:45 +00:00
firstName := fmt . Sprintf ( "%v" , userRawData [ "first_name" ] )
lastName := fmt . Sprintf ( "%v" , userRawData [ "last_name" ] )
picture := fmt . Sprintf ( "%v" , picDataObject [ "url" ] )
2023-11-09 08:15:29 +00:00
user := & models . User {
2021-12-22 10:01:45 +00:00
GivenName : & firstName ,
FamilyName : & lastName ,
Picture : & picture ,
2023-10-25 19:25:10 +00:00
Email : & email ,
2021-09-04 22:27:29 +00:00
}
2021-10-13 16:41:41 +00:00
return user , nil
2021-09-04 22:27:29 +00:00
}
2022-06-06 16:38:32 +00:00
2023-08-17 08:50:31 +00:00
func processLinkedInUserInfo ( ctx context . Context , code string ) ( * models . User , error ) {
oauth2Token , err := oauth . OAuthProviders . LinkedInConfig . Exchange ( ctx , code )
2022-06-06 16:38:32 +00:00
if err != nil {
log . Debug ( "Failed to exchange code for token: " , err )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "invalid linkedin exchange code: %s" , err . Error ( ) )
2022-06-06 16:38:32 +00:00
}
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 08:15:29 +00:00
return nil , fmt . Errorf ( "error creating linkedin user info request: %s" , err . Error ( ) )
2022-06-06 16:38:32 +00:00
}
req . Header = http . Header {
2022-06-12 13:00:33 +00:00
"Authorization" : [ ] string { fmt . Sprintf ( "Bearer %s" , oauth2Token . AccessToken ) } ,
2022-06-06 16:38:32 +00:00
}
response , err := client . Do ( req )
if err != nil {
log . Debug ( "Failed to request linkedin user info: " , err )
2023-11-09 08:15:29 +00:00
return nil , err
2022-06-06 16:38:32 +00:00
}
defer response . Body . Close ( )
2022-12-23 17:19:44 +00:00
body , err := io . ReadAll ( response . Body )
2022-06-06 16:38:32 +00:00
if err != nil {
log . Debug ( "Failed to read linkedin user info response body: " , err )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "failed to read linkedin response body: %s" , err . Error ( ) )
2022-06-06 16:38:32 +00:00
}
if response . StatusCode >= 400 {
log . Debug ( "Failed to request linkedin user info: " , string ( body ) )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "failed to request linkedin user info: %s" , string ( body ) )
2022-06-06 16:38:32 +00:00
}
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 08:15:29 +00:00
return nil , fmt . Errorf ( "error creating linkedin user info request: %s" , err . Error ( ) )
2022-06-06 16:38:32 +00:00
}
req . Header = http . Header {
2022-06-12 13:00:33 +00:00
"Authorization" : [ ] string { fmt . Sprintf ( "Bearer %s" , oauth2Token . AccessToken ) } ,
2022-06-06 16:38:32 +00:00
}
response , err = client . Do ( req )
if err != nil {
log . Debug ( "Failed to request linkedin email info: " , err )
2023-11-09 08:15:29 +00:00
return nil , err
2022-06-06 16:38:32 +00:00
}
defer response . Body . Close ( )
2022-12-23 17:19:44 +00:00
body , err = io . ReadAll ( response . Body )
2022-06-06 16:38:32 +00:00
if err != nil {
log . Debug ( "Failed to read linkedin email info response body: " , err )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "failed to read linkedin email response body: %s" , err . Error ( ) )
2022-06-06 16:38:32 +00:00
}
if response . StatusCode >= 400 {
log . Debug ( "Failed to request linkedin user info: " , string ( body ) )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "failed to request linkedin user info: %s" , string ( body ) )
2022-06-06 16:38:32 +00:00
}
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 08:15:29 +00:00
user := & models . User {
2022-06-06 16:38:32 +00:00
GivenName : & firstName ,
FamilyName : & lastName ,
Picture : & profilePicture ,
2023-10-25 19:25:10 +00:00
Email : & emailAddress ,
2022-06-06 16:38:32 +00:00
}
return user , nil
}
2022-06-12 13:00:33 +00:00
2023-08-17 08:50:31 +00:00
func processAppleUserInfo ( ctx context . Context , code string ) ( * models . User , error ) {
2023-11-09 08:15:29 +00:00
var user = & models . User { }
2023-08-17 08:50:31 +00:00
oauth2Token , err := oauth . OAuthProviders . AppleConfig . Exchange ( ctx , code )
2022-06-12 13:00:33 +00:00
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 06:08:04 +00:00
tokenSplit := strings . Split ( rawIDToken , "." )
claimsData := tokenSplit [ 1 ]
2022-06-15 16:25:41 +00:00
decodedClaimsData , err := base64 . RawURLEncoding . DecodeString ( claimsData )
2022-06-12 13:00:33 +00:00
if err != nil {
2022-06-15 16:25:41 +00:00
log . Debugf ( "Failed to decrypt claims %s: %s" , claimsData , err . Error ( ) )
2022-06-14 06:08:04 +00:00
return user , fmt . Errorf ( "failed to decrypt claims data: %s" , err . Error ( ) )
}
2022-06-14 07:05:23 +00:00
claims := make ( map [ string ] interface { } )
2022-06-15 16:25:41 +00:00
err = json . Unmarshal ( decodedClaimsData , & claims )
2022-06-14 06:08:04 +00:00
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 13:00:33 +00:00
}
2023-11-09 08:15:29 +00:00
if val , ok := claims [ "email" ] ; ! ok || val == nil {
2022-06-14 08:07:05 +00:00
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 07:05:23 +00:00
} else {
2023-10-25 19:25:10 +00:00
email := val . ( string )
user . Email = & email
2022-06-14 07:05:23 +00:00
}
if val , ok := claims [ "name" ] ; ok {
2022-06-14 07:41:39 +00:00
nameData := val . ( map [ string ] interface { } )
2022-06-14 10:15:06 +00:00
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 07:05:23 +00:00
}
2022-06-12 13:00:33 +00:00
return user , err
}
2022-08-13 07:05:00 +00:00
2023-08-17 08:50:31 +00:00
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 18:19:48 +00:00
if err != nil {
log . Debug ( "Failed to exchange code for token: " , err )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "invalid twitter exchange code: %s" , err . Error ( ) )
2022-08-14 18:19:48 +00: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 08:15:29 +00:00
return nil , fmt . Errorf ( "error creating Twitter user info request: %s" , err . Error ( ) )
2022-08-14 18:19:48 +00: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 08:15:29 +00:00
return nil , err
2022-08-14 18:19:48 +00:00
}
defer response . Body . Close ( )
2022-12-23 17:19:44 +00:00
body , err := io . ReadAll ( response . Body )
2022-08-14 18:19:48 +00:00
if err != nil {
log . Debug ( "Failed to read Twitter user info response body: " , err )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "failed to read Twitter response body: %s" , err . Error ( ) )
2022-08-14 18:19:48 +00:00
}
if response . StatusCode >= 400 {
log . Debug ( "Failed to request Twitter user info: " , string ( body ) )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "failed to request Twitter user info: %s" , string ( body ) )
2022-08-14 18:19:48 +00:00
}
responseRawData := make ( map [ string ] interface { } )
json . Unmarshal ( body , & responseRawData )
userRawData := responseRawData [ "data" ] . ( map [ string ] interface { } )
2022-08-29 02:49:11 +00:00
// log.Info(userRawData)
2022-08-14 18:19:48 +00: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 08:15:29 +00:00
user := & models . User {
2022-08-14 18:19:48 +00:00
GivenName : & firstName ,
FamilyName : & lastName ,
Picture : & profilePicture ,
Nickname : & nickname ,
}
2022-08-13 07:05:00 +00:00
return user , nil
}
2023-02-25 23:53:02 +00:00
// process microsoft user information
2023-08-17 08:50:31 +00:00
func processMicrosoftUserInfo ( ctx context . Context , code string ) ( * models . User , error ) {
2023-02-25 23:53:02 +00:00
oauth2Token , err := oauth . OAuthProviders . MicrosoftConfig . Exchange ( ctx , code )
if err != nil {
log . Debug ( "Failed to exchange code for token: " , err )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "invalid microsoft exchange code: %s" , err . Error ( ) )
2023-02-25 23:53:02 +00:00
}
2023-08-17 08:50:31 +00:00
// 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-25 23:53:02 +00:00
// 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 08:15:29 +00:00
return nil , fmt . Errorf ( "unable to extract id_token" )
2023-02-25 23:53:02 +00:00
}
// 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 08:15:29 +00:00
return nil , fmt . Errorf ( "unable to verify id_token: %s" , err . Error ( ) )
2023-02-25 23:53:02 +00:00
}
2023-11-09 08:15:29 +00:00
user := & models . User { }
2023-02-25 23:53:02 +00:00
if err := idToken . Claims ( & user ) ; err != nil {
log . Debug ( "Failed to parse ID Token claims: " , err )
2023-11-09 08:15:29 +00:00
return nil , fmt . Errorf ( "unable to extract claims" )
2023-02-25 23:53:02 +00:00
}
return user , nil
}
2023-12-02 06:51:53 +00:00
// 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
}