93 lines
2.4 KiB
Go
93 lines
2.4 KiB
Go
package sso
|
|
|
|
import (
|
|
"fmt"
|
|
"errors"
|
|
"context"
|
|
"net/http"
|
|
"github.com/coreos/go-oidc/v3/oidc"
|
|
"golang.org/x/oauth2"
|
|
)
|
|
|
|
type GoogleOAuthProvider struct {
|
|
RedirectURI string
|
|
OAuth2Config *oauth2.Config
|
|
Verifier *oidc.IDTokenVerifier
|
|
Cancel context.CancelFunc
|
|
HostedDomain string
|
|
}
|
|
|
|
|
|
func (g *GoogleOAuthProvider) BuildAuthURL(state string) (string, error) {
|
|
var opts []oauth2.AuthCodeOption
|
|
|
|
// set hosted domain. google supports multiple hosted domains but in our case
|
|
// we have one config per host domain.
|
|
opts = append(opts, oauth2.SetAuthURLParam("hd", g.HostedDomain))
|
|
|
|
return g.OAuth2Config.AuthCodeURL(state, opts...), nil
|
|
}
|
|
|
|
type oauth2Error struct{
|
|
error string
|
|
errorDescription string
|
|
}
|
|
|
|
func (e *oauth2Error) Error() string {
|
|
if e.errorDescription == "" {
|
|
return e.error
|
|
}
|
|
return e.error + ": " + e.errorDescription
|
|
}
|
|
|
|
func (g *GoogleOAuthProvider) HandleCallback(r *http.Request) (identity *SSOIdentity, err error) {
|
|
q := r.URL.Query()
|
|
if errType := q.Get("error"); errType != "" {
|
|
return identity, &oauth2Error{errType, q.Get("error_description")}
|
|
}
|
|
|
|
token, err := g.OAuth2Config.Exchange(r.Context(), q.Get("code"))
|
|
if err != nil {
|
|
return identity, fmt.Errorf("google: failed to get token: %v", err)
|
|
}
|
|
|
|
return g.createIdentity(r.Context(), token)
|
|
}
|
|
|
|
|
|
func (g *GoogleOAuthProvider) createIdentity(ctx context.Context, token *oauth2.Token) (identity *SSOIdentity, err error) {
|
|
rawIDToken, ok := token.Extra("id_token").(string)
|
|
if !ok {
|
|
return identity, errors.New("google: no id_token in token response")
|
|
}
|
|
idToken, err := g.Verifier.Verify(ctx, rawIDToken)
|
|
if err != nil {
|
|
return identity, fmt.Errorf("google: failed to verify ID Token: %v", err)
|
|
}
|
|
|
|
var claims struct {
|
|
Username string `json:"name"`
|
|
Email string `json:"email"`
|
|
EmailVerified bool `json:"email_verified"`
|
|
HostedDomain string `json:"hd"`
|
|
}
|
|
if err := idToken.Claims(&claims); err != nil {
|
|
return identity, fmt.Errorf("oidc: failed to decode claims: %v", err)
|
|
}
|
|
|
|
if claims.HostedDomain != g.HostedDomain {
|
|
return identity, fmt.Errorf("oidc: unexpected hd claim %v", claims.HostedDomain)
|
|
}
|
|
|
|
identity = &SSOIdentity{
|
|
UserID: idToken.Subject,
|
|
Username: claims.Username,
|
|
Email: claims.Email,
|
|
EmailVerified: claims.EmailVerified,
|
|
ConnectorData: []byte(token.RefreshToken),
|
|
}
|
|
|
|
return identity, nil
|
|
}
|
|
|