package main import ( "context" "encoding/hex" "flag" "fmt" "log" "net/http" "golang.org/x/oauth2" "github.com/coreos/go-oidc/v3/oidc" "github.com/julienschmidt/httprouter" "git.nemunai.re/srs/adlin/libadlin" ) var ( oidcClientID = "" oidcSecret = "" oauth2Config oauth2.Config oidcVerifier *oidc.IDTokenVerifier ) func init() { flag.StringVar(&oidcClientID, "oidc-clientid", oidcClientID, "ClientID for OIDC") flag.StringVar(&oidcSecret, "oidc-secret", oidcSecret, "Secret for OIDC") router.GET("/auth/CRI", redirectOIDC_CRI) router.GET("/auth/complete", OIDC_CRI_complete) } func initializeOIDC() { if oidcClientID != "" && oidcSecret != "" { provider, err := oidc.NewProvider(context.Background(), "https://cri.epita.fr") if err != nil { log.Fatal("Unable to setup oidc:", err) } oauth2Config = oauth2.Config{ ClientID: oidcClientID, ClientSecret: oidcSecret, RedirectURL: "https://adlin.nemunai.re" + baseURL + "/auth/complete", // Discovery returns the OAuth2 endpoints. Endpoint: provider.Endpoint(), // "openid" is a required scope for OpenID Connect flows. Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, } oidcConfig := oidc.Config{ ClientID: oidcClientID, } oidcVerifier = provider.Verifier(&oidcConfig) } } func redirectOIDC_CRI(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { session, err := adlin.NewSession() if err != nil { http.Error(w, fmt.Sprintf("{'errmsg':%q}", err.Error()), http.StatusInternalServerError) } else { http.Redirect(w, r, oauth2Config.AuthCodeURL(hex.EncodeToString(session.Id)), http.StatusFound) } } func OIDC_CRI_complete(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { idsession, err := hex.DecodeString(r.URL.Query().Get("state")) if err != nil { http.Error(w, fmt.Sprintf("{'errmsg':%q}", err.Error()), http.StatusBadRequest) return } session, err := adlin.GetSession(idsession) if err != nil { http.Error(w, fmt.Sprintf("{'errmsg':%q}", err.Error()), http.StatusBadRequest) return } oauth2Token, err := oauth2Config.Exchange(context.Background(), r.URL.Query().Get("code")) if err != nil { http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError) return } rawIDToken, ok := oauth2Token.Extra("id_token").(string) if !ok { http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError) return } idToken, err := oidcVerifier.Verify(context.Background(), rawIDToken) if err != nil { http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError) return } var claims struct { Firstname string `json:"given_name"` Lastname string `json:"family_name"` Nickname string `json:"nickname"` Username string `json:"preferred_username"` Email string `json:"email"` } if err := idToken.Claims(&claims); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if err := completeAuth(w, claims.Username, session); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } http.Redirect(w, r, "/", http.StatusFound) }