diff --git a/auth.go b/auth.go index 2937150..4f0a710 100644 --- a/auth.go +++ b/auth.go @@ -10,6 +10,21 @@ import ( "time" ) +type User struct { + Sub string `json:"sub"` +} + +type AuthentikUser struct { + User + Email string `json:"email"` + EmailVerified bool `json:"email_verified"` + Name string `json:"name"` + GivenName string `json:"given_name"` + PreferredUsername string `json:"preferred_username"` + Nickname string `json:"nickname"` + Groups []string `json:"groups"` +} + type Auth struct { authConfig AuthConfig clientConfig ClientConfig @@ -91,6 +106,36 @@ func (a Auth) GetAuthorizationURL(state string) (string, error) { return url.String(), nil } +func (a Auth) GetUserInfo(accessToken string, user any) error { + req, err := http.NewRequest("GET", a.authConfig.UserinfoEndpoint, nil) + + req.Header.Add("Authorization", "Bearer "+accessToken) + + hc := http.Client{} + resp, err := hc.Do(req) + if err != nil { + return fmt.Errorf("%w: %q", ErrCreateRequestForUserInfo, err) + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("%w: %s (%v)", ErrCantGetUserInfo, "server response with nuon 200 status code", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("%w: %q", ErrCreateRequestForUserInfo, err) + } + + err = json.Unmarshal(body, &user) + if err != nil { + return fmt.Errorf("%w: %s: %q", ErrCantGetUserInfo, "json unmarshal", err) + } + + return nil +} + func (a Auth) GetTokenFromCode(code string) (Token, error) { form := url.Values{} form.Add("grant_type", "authorization_code") diff --git a/auth_test.go b/auth_test.go index 7b36124..f1fbfb6 100644 --- a/auth_test.go +++ b/auth_test.go @@ -127,16 +127,67 @@ func TestUseCodeToGetToken(t *testing.T) { } } +func TestGetUserInfo(t *testing.T) { + tts := []struct { + name string + accessToken string + userInfoUrl string + exptError error + exptErrorMsg string + exptUsername string + exptNickname string + }{ + { + name: "token-invalide", + accessToken: "abc", + userInfoUrl: "http://localhost:8084/userinfo/authentik-success.json", + exptUsername: "exampleusername", + exptNickname: "mynickname", + }, + { + name: "token-invalide", + accessToken: "abc", + userInfoUrl: "http://localhost:8084/userinfo/authentik-error.json", + exptErrorMsg: "cant get user info: server response with nuon 200 status code (400)", + exptError: ErrCantGetUserInfo, + }, + } + + for _, tt := range tts { + t.Run(tt.name, func(t *testing.T) { + ClientConfig := ClientConfig{ClientID: "abc", ClientSecret: "def", RedirectURL: "http://localhost/something"} + AuthConfig := AuthConfig{UserinfoEndpoint: tt.userInfoUrl} + client, err := NewAuthWithConfig(ClientConfig, AuthConfig) + assert.Nil(t, err, "should be abel to create client without error") + + u := AuthentikUser{} + err = client.GetUserInfo(tt.accessToken, &u) + + assert.ErrorIs(t, err, tt.exptError, "should return right error") + + if tt.exptErrorMsg != "" { + assert.Equal(t, tt.exptErrorMsg, err.Error(), "should return right error string") + } + + assert.Equal(t, tt.exptUsername, u.PreferredUsername, "should have right user") + assert.Equal(t, tt.exptNickname, u.Nickname, "should have right nickname") + }) + } +} + func TestAuthenticLogin(t *testing.T) { - t.Skip("dev test") - clientConfig := ClientConfig{ClientID: "abc", ClientSecret: "abc", RedirectURL: "http://localhost/somethingelse"} + t.Skip("dev") + clientConfig := ClientConfig{ClientID: "hTqEFr0CyS3XVWYC0folnZlU34JdjpRQmjpyhrQR", ClientSecret: "T6CcDWGWMshSLYbRCJ6yfYEphAVUEeeDii9k9o8uECY2ZRPovf2gPiC486W1DSKxIvOcyk2Y0iorBZRO4sbjNEvkfhbMYuEJAKAUk7mD3C7SPAb1MHl79PcZdMn2rdrp", RedirectURL: "http://localhost/somethingelse"} client, err := NewAuthWithConfigurationURL(clientConfig, "http://localhost:8084/openid-configuration") assert.Nil(t, err, "should be able to create client without error") url, err := client.GetAuthorizationURL("") assert.Nil(t, err, "should be able to create url without error") fmt.Println(url) - token, err := client.GetTokenFromCode("9aa96340040342e5a7df969834d9e278") + token, err := client.GetTokenFromCode("0126cbf9d9034fdfbc7b03cff191dc5d") assert.Nil(t, err, "should be able to get code without error") - fmt.Println(token) + fmt.Println(token.AccessToken) + + u := User{} + client.GetUserInfo("eyJhbGciOiJSUzI1NiIsImtpZCI6IjQ5ZGRiNmI0YzAxMmEyNjE2NWVhZDY5NTc5YWU1MWE5IiwidHlwIjoiSldUIn0.eyJpc3MiOiJodHRwczovL2F1dGgua2Vrcy5jbG91ZC9hcHBsaWNhdGlvbi9vL3Rlc3QvIiwic3ViIjoiNTE5NGYyZTViNmRlOGEwOTQxODEwM2FkY2ZkYzM0NTMzZjAxYWNjM2Q5YzIwZDM5NzZiNzI1YjE3MmFhMmE0MyIsImF1ZCI6ImhUcUVGcjBDeVMzWFZXWUMwZm9sblpsVTM0SmRqcFJRbWpweWhyUVIiLCJleHAiOjE3MjYzMDY3MjgsImlhdCI6MTcyNjMwNjQyOCwiYXV0aF90aW1lIjoxNzI0NDAzMjczLCJhY3IiOiJnb2F1dGhlbnRpay5pby9wcm92aWRlcnMvb2F1dGgyL2RlZmF1bHQiLCJlbWFpbCI6InNvZXJlbkBzaWVnaXNtdW5kLXBvc2NobWFubi5ldSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiU1x1MDBmNnJlbiIsImdpdmVuX25hbWUiOiJTXHUwMGY2cmVuIiwicHJlZmVycmVkX3VzZXJuYW1lIjoia2Vrc2t1cnNlIiwibmlja25hbWUiOiJrZWtza3Vyc2UiLCJncm91cHMiOlsiYXV0aGVudGlrIEFkbWlucyIsIk5leHRjbG91ZCIsImdpdHVzZXIiLCJnaXRhZG1pbiJdLCJhenAiOiJoVHFFRnIwQ3lTM1hWV1lDMGZvbG5abFUzNEpkanBSUW1qcHloclFSIiwidWlkIjoiazhVUkFXdmpoNnlCSUdIN1JNUWNXZXpVZmF1OWFQRHZGa2tRVXd0SiJ9.il3HHGcVXL260sx1D9D8zvoSF7aIqbBKQVllTs7Giqej_3PBdFID9LQFRt9i0-izTw0M3RVnJ19xLNUZVSXyaRq1CPhuqUxA0fM3DJXfOxesD6pfhW9P92-U8fj_M4VxUwl_XAuWRB_5ynBii5HL4cdia89v4KyY2gohRUoUGvMLMN3qCT1WvS8RPQ--3MsHBi322C2NaPd2QX1TNXnYSaKRT0OQTUDRUopsp7R0KSNppngU813x9oiKL62UxGJ5ZRZ3OPTv0S_rV3Y9Ql9z8nmgcEW5ohckLFiTcb9v1HVr8XoKTU63g0REBkA3ZGh1RNDC99m0P3D_bDqni-fT3rSOOEW2x9gUOjX2SjKv2p4gRU9iHYSO1SCPk68ICTyogtwtHlM7IgGqdwoz10hGijkrOtq6cwWRwWZx6qYRV6TtEwbkEubKeanXOIF_eipUiemc5A-0xFKKC4BTJHrMVXWhKLZoPHYaog8MBMxzm8Hrf4cjfqCfFt1504J2ywUTHERRFr3031QNtICAjOYqrD59KcnCNdU0KztHa0trDfypkk-X_0Cxe0kG2CZX0fc21fQFBLewoTZ1FkOglMu6Yj_Wn7AjtBFQ1dGeWbxi6UJh0B9o2AiSrrOy392D5OTlwvD_Zmy-1c4Ijq5lDd7RbBhEr-pA7Eaz4PagyoAUCnk", &u) } diff --git a/errors.go b/errors.go index 68ec80b..0c862f9 100644 --- a/errors.go +++ b/errors.go @@ -9,4 +9,6 @@ var ( ErrCantSendRequestsForToken = errors.New("cant send requests for token with code") ErrCantGetTokenForCode = errors.New("cant get oauth token with code") ErrWrongResponseFromServer = errors.New("cant get access token from server") + ErrCantGetUserInfo = errors.New("cant get user info") + ErrCreateRequestForUserInfo = errors.New("cant create request for get user") ) diff --git a/minimock/config.yml b/minimock/config.yml index b8f264d..b62dd02 100644 --- a/minimock/config.yml +++ b/minimock/config.yml @@ -10,3 +10,8 @@ routen: - path: /token/invalide-response response_body: somethings was really wrong response_http_status: 500 + - path: /userinfo/authentik-error.json + response_file: /data/userinfo/authentik-error.json + response_http_status: 400 + - path: /userinfo/authentik-success.json + response_file: /data/userinfo/authentik-success.json diff --git a/minimock/userinfo/authentik-error.json b/minimock/userinfo/authentik-error.json new file mode 100644 index 0000000..aaaa8a4 --- /dev/null +++ b/minimock/userinfo/authentik-error.json @@ -0,0 +1 @@ +{"error": "invalid_grant", "error_description": "The provided authorization grant or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client"} diff --git a/minimock/userinfo/authentik-success.json b/minimock/userinfo/authentik-success.json new file mode 100644 index 0000000..5783e89 --- /dev/null +++ b/minimock/userinfo/authentik-success.json @@ -0,0 +1 @@ +{"sub": "5194f2e5b6de8a09418103adcfdc34533f01acc3d2c20d396b725b172aa2a43", "email": "some@eample.com", "email_verified": true, "name": "L\u00f6ren", "given_name": "L\u00f6ren", "preferred_username": "exampleusername", "nickname": "mynickname", "groups": ["authentik Admins", "randomOtherGroupn"]}