diff --git a/.gitignore b/.gitignore index a15ed80..0e6d89c 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ config.yaml # Exclude air temp directory target/ + +#Exclude values.yaml +/config/values.yaml \ No newline at end of file diff --git a/api/main.go b/api/main.go index 49fc607..f34c83d 100644 --- a/api/main.go +++ b/api/main.go @@ -35,7 +35,8 @@ func Start() { r.GET("/recovery", HandleGetRecoveryFlow) r.POST("/recovery", HandlePostRecoveryFlow) - + r.POST("/login/oidc/:provider", HandleOIDCLogin) + r.POST("/register/oidc/:provider", HandleOIDCRegister) r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") } diff --git a/api/oidc.go b/api/oidc.go new file mode 100644 index 0000000..85c82df --- /dev/null +++ b/api/oidc.go @@ -0,0 +1,104 @@ +package api + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/sdslabs/nymeria/log" + "github.com/sdslabs/nymeria/pkg/wrapper/kratos/login" + "github.com/sdslabs/nymeria/pkg/wrapper/kratos/oidc" + "github.com/sdslabs/nymeria/pkg/wrapper/kratos/registration" +) + +func HandleOIDCLogin(c *gin.Context) { + log.Logger.Debug("Get OIDC Login") + provider := c.Param("provider") + if provider == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "provider not found", + }) + return + } + cookie, flowID, csrf_token, err := login.InitializeLoginFlowWrapper() + + if err != nil { + log.ErrorLogger("Intialize OIDC Login Failed", err) + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "internal server error", + }) + return + } + c.SetCookie("OIDC_login_flow", cookie, 3600, "/", "localhost", false, true) + //In case we need to separate the flows so setting and getting cookies simultaneously + afterCookie, err := c.Cookie("OIDC_login_flow") + + if err != nil { + log.ErrorLogger("Cookie not found", err) + c.JSON(http.StatusBadRequest, gin.H{ + "error": "csrf cookie not found", + }) + return + } + + session, err := oidc.SubmitOIDCLoginFlowWrapper(provider, afterCookie, flowID, csrf_token) + + if err != nil { + log.ErrorLogger("Kratos post OIDC login flow failed", err) + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "internal server error", + }) + return + } + + c.SetCookie("sdslabs_session", session, 3600, "/", "localhost", false, true) + c.JSON(http.StatusOK, gin.H{ + "status": "user logged in via OIDC", + }) + +} + +func HandleOIDCRegister(c *gin.Context) { + log.Logger.Debug("Get OIDC Registration") + provider := c.Param("provider") + if provider == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "provider not found", + }) + return + } + cookie, flowID, csrf_token, err := registration.InitializeRegistrationFlowWrapper() + + if err != nil { + log.ErrorLogger("Kratos get OIDC registration flow failed", err) + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "internal server error", + }) + return + } + + c.SetCookie("OIDC_registration_flow", cookie, 3600, "/", "localhost", false, true) + //In case we need to separate the flows so setting and getting cookies simultaneously + afterCookie, err := c.Cookie("OIDC_registration_flow") + + if err != nil { + log.ErrorLogger("Cookie not found", err) + c.JSON(http.StatusBadRequest, gin.H{ + "error": "csrf cookie not found", + }) + return + } + session, err := oidc.SubmitOIDCRegistrationFlowWrapper(provider, afterCookie, flowID, csrf_token) + + if err != nil { + log.ErrorLogger("Kratos OIDC post registration flow failed", err) + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "internal server error", + }) + return + } + c.SetCookie("sdslabs_session", session, 3600, "/", "localhost", false, true) + c.JSON(http.StatusOK, gin.H{ + "status": "created via OIDC", + }) + +} \ No newline at end of file diff --git a/config/kratos_config.yaml b/config/kratos_config.yaml index ead11c9..8b20109 100644 --- a/config/kratos_config.yaml +++ b/config/kratos_config.yaml @@ -1,5 +1,30 @@ selfservice: strategies: + oidc: + config: + providers: + - id: google # this is `` in the Authorization callback URL. DO NOT CHANGE IT ONCE SET! + provider: google + client_id: {{google.client_id}} # Replace this with the OAuth2 Client ID + client_secret: {{google.client_secret}} # Replace this with the OAuth2 Client secret + mapper_url: file:///etc/config/kratos/oidc.google.jsonnet + # Alternatively, use an URL: + # mapper_url: https://storage.googleapis.com/abc-cde-prd/9cac9717f007808bf17f22ce7f4295c739604b183f05ac4afb4 + scope: + - email + - profile + # other supported scopes can be found in Google OAuth 2.0 dev docs + requested_claims: + id_token: + email: + essential: true + email_verified: + essential: true + given_name: + essential: true + family_name: null + hd: null # If you want the Google Workspace domain + enabled: true password: enabled: true diff --git a/config/oidc.google.jsonnet b/config/oidc.google.jsonnet new file mode 100644 index 0000000..b03ff9f --- /dev/null +++ b/config/oidc.google.jsonnet @@ -0,0 +1,11 @@ +local claims = { + email_verified: true, +} + std.extVar('claims'); + +{ + identity: { + traits: { + [if 'email' in claims && claims.email_verified then 'email' else null]: claims.email, + }, + }, +} \ No newline at end of file diff --git a/pkg/controller/admin/identity.go b/pkg/controller/admin/identity.go index 63e1980..8a650a2 100644 --- a/pkg/controller/admin/identity.go +++ b/pkg/controller/admin/identity.go @@ -7,7 +7,6 @@ import ( "net/http" "os" "strconv" - "strings" "github.com/gin-gonic/gin" client "github.com/ory/client-go" diff --git a/pkg/wrapper/kratos/oidc/oidc.go b/pkg/wrapper/kratos/oidc/oidc.go new file mode 100644 index 0000000..bf79d92 --- /dev/null +++ b/pkg/wrapper/kratos/oidc/oidc.go @@ -0,0 +1,44 @@ +package oidc + +import ( + "context" + "fmt" + "os" + + client "github.com/ory/client-go" + "github.com/sdslabs/nymeria/config" +) + +func SubmitOIDCRegistrationFlowWrapper(provider string, cookie string, flowID string, csrfToken string) (string, error) { + submitOIDCRegistrationFlowBody := client.SubmitSelfServiceRegistrationFlowBody{ + SubmitSelfServiceRegistrationFlowWithOidcMethodBody: client.NewSubmitSelfServiceRegistrationFlowWithOidcMethodBody("oidc", provider), + } + + submitOIDCRegistrationFlowBody.SubmitSelfServiceRegistrationFlowWithOidcMethodBody.SetCsrfToken(csrfToken) + + apiClient := client.NewAPIClient(config.KratosClientConfig) + _, r, err := apiClient.V0alpha2Api.SubmitSelfServiceRegistrationFlow(context.Background()).Flow(flowID).SubmitSelfServiceRegistrationFlowBody(submitOIDCRegistrationFlowBody).Cookie(cookie).Execute() + if err != nil { + fmt.Fprintf(os.Stderr, "Error when calling `V0alpha2Api.SubmitSelfServiceRegistrationFlow``: %v\n", err) + fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) + } + + responseCookies := r.Header["Set-Cookie"] + return responseCookies[1], nil +} + +func SubmitOIDCLoginFlowWrapper(provider string, cookie string, flowID string, csrfToken string) (string, error) { + submitOIDCLoginFlowBody := client.SubmitSelfServiceLoginFlowBody{SubmitSelfServiceLoginFlowWithOidcMethodBody: client.NewSubmitSelfServiceLoginFlowWithOidcMethodBody("oidc", provider)} // SubmitSelfServiceLoginFlowBody | + + submitOIDCLoginFlowBody.SubmitSelfServiceLoginFlowWithOidcMethodBody.SetCsrfToken(csrfToken) + + apiClient := client.NewAPIClient(config.KratosClientConfig) + _, r, err := apiClient.V0alpha2Api.SubmitSelfServiceLoginFlow(context.Background()).Flow(flowID).SubmitSelfServiceLoginFlowBody(submitOIDCLoginFlowBody).XSessionToken("").Cookie(cookie).Execute() + if err != nil { + return "", err + } + + responseCookies := r.Header["Set-Cookie"] + + return responseCookies[1], nil +}