2024-09-26 09:06:17 +00:00
|
|
|
package auth
|
2024-09-12 17:39:24 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"strings"
|
2024-09-13 09:05:36 +00:00
|
|
|
"time"
|
2024-09-12 17:39:24 +00:00
|
|
|
)
|
|
|
|
|
2024-09-15 18:42:07 +00:00
|
|
|
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"`
|
|
|
|
}
|
|
|
|
|
2024-09-12 17:39:24 +00:00
|
|
|
type Auth struct {
|
2024-09-13 09:05:36 +00:00
|
|
|
authConfig AuthConfig
|
|
|
|
clientConfig ClientConfig
|
2024-09-12 17:39:24 +00:00
|
|
|
}
|
|
|
|
|
2024-09-13 09:05:36 +00:00
|
|
|
type Token struct {
|
|
|
|
AccessToken string `json:"access_token"`
|
|
|
|
TokenType string `json:"token_type"`
|
|
|
|
ExpiredIn int `json:"expires_in"`
|
|
|
|
RefreshToken string `json:"refresh_token"`
|
|
|
|
CreatedAt time.Time
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewAuthWithConfig(config ClientConfig, authConfig AuthConfig) (Auth, error) {
|
2024-09-12 17:39:24 +00:00
|
|
|
a := Auth{}
|
2024-09-13 09:05:36 +00:00
|
|
|
a.authConfig = authConfig
|
|
|
|
a.clientConfig = config
|
2024-09-12 17:39:24 +00:00
|
|
|
return a, nil
|
|
|
|
}
|
|
|
|
|
2024-09-13 09:05:36 +00:00
|
|
|
func NewAuthWithConfigurationURL(config ClientConfig, url string) (Auth, error) {
|
2024-09-12 17:39:24 +00:00
|
|
|
a := Auth{}
|
2024-09-13 09:05:36 +00:00
|
|
|
a.clientConfig = config
|
|
|
|
authConfig := AuthConfig{}
|
2024-09-12 17:39:24 +00:00
|
|
|
|
|
|
|
res, err := http.Get(url)
|
|
|
|
if err != nil {
|
|
|
|
return Auth{}, fmt.Errorf("%w: %q", ErrCantGetConfiguratorData, err)
|
|
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
|
|
|
|
bodyContent, err := io.ReadAll(res.Body)
|
|
|
|
if err != nil {
|
|
|
|
return Auth{}, fmt.Errorf("%w: %q", ErrCantGetConfiguratorData, err)
|
|
|
|
}
|
|
|
|
|
2024-09-13 09:05:36 +00:00
|
|
|
err = json.Unmarshal(bodyContent, &authConfig)
|
2024-09-12 17:39:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return Auth{}, fmt.Errorf("%w: %q", ErrCantGetConfiguratorData, err)
|
|
|
|
}
|
|
|
|
|
2024-09-13 09:05:36 +00:00
|
|
|
a.authConfig = authConfig
|
2024-09-12 17:39:24 +00:00
|
|
|
return a, nil
|
|
|
|
}
|
|
|
|
|
2024-09-13 09:05:36 +00:00
|
|
|
func (a Auth) GetAuthorizationURL(state string) (string, error) {
|
|
|
|
if a.authConfig.AuthorizationEndpoint == "" {
|
2024-09-12 17:39:24 +00:00
|
|
|
return "", fmt.Errorf("%w: %s", ErrCantGetAuthorizationURL, "AuthorizationEndpoint in config is empty")
|
|
|
|
}
|
|
|
|
|
2024-09-13 09:05:36 +00:00
|
|
|
if a.clientConfig.ClientID == "" {
|
2024-09-12 17:39:24 +00:00
|
|
|
return "", fmt.Errorf("%w: %s", ErrCantGetAuthorizationURL, "clientid in config is empty")
|
|
|
|
}
|
|
|
|
|
2024-09-13 09:05:36 +00:00
|
|
|
url, err := url.Parse(a.authConfig.AuthorizationEndpoint)
|
2024-09-12 17:39:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("%w: %q", ErrCantGetAuthorizationURL, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
values := url.Query()
|
|
|
|
|
2024-09-13 09:05:36 +00:00
|
|
|
values.Set("client_id", a.clientConfig.ClientID)
|
|
|
|
if a.clientConfig.RedirectURL != "" {
|
|
|
|
values.Set("redirect_uri", a.clientConfig.RedirectURL)
|
2024-09-12 17:39:24 +00:00
|
|
|
}
|
|
|
|
|
2024-09-13 09:05:36 +00:00
|
|
|
if len(a.clientConfig.Scope) > 0 {
|
|
|
|
values.Set("scope", strings.Join(a.clientConfig.Scope, "+"))
|
2024-09-12 17:39:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if state != "" {
|
|
|
|
values.Set("state", state)
|
|
|
|
}
|
|
|
|
|
2024-09-13 08:27:57 +00:00
|
|
|
values.Set("response_type", "code")
|
|
|
|
|
2024-09-12 17:39:24 +00:00
|
|
|
url.RawQuery = values.Encode()
|
|
|
|
|
|
|
|
return url.String(), nil
|
|
|
|
}
|
2024-09-13 09:05:36 +00:00
|
|
|
|
2024-09-15 18:42:07 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-09-13 09:05:36 +00:00
|
|
|
func (a Auth) GetTokenFromCode(code string) (Token, error) {
|
|
|
|
form := url.Values{}
|
|
|
|
form.Add("grant_type", "authorization_code")
|
|
|
|
form.Add("code", code)
|
|
|
|
|
2024-09-13 21:41:29 +00:00
|
|
|
req, err := http.NewRequest("POST", a.authConfig.TokenEndpoint, strings.NewReader(form.Encode()))
|
|
|
|
if err != nil {
|
|
|
|
return Token{}, fmt.Errorf("%w: %q", ErrCantCreateTokenRequests, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
|
|
|
req.SetBasicAuth(a.clientConfig.ClientID, a.clientConfig.ClientSecret)
|
|
|
|
|
|
|
|
hc := http.Client{}
|
|
|
|
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return Token{}, fmt.Errorf("%w: %q", ErrCantSendRequestsForToken, err)
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
return Token{}, fmt.Errorf("%w: %q", ErrCantSendRequestsForToken, err)
|
|
|
|
}
|
|
|
|
fmt.Println(string(body))
|
|
|
|
fmt.Println(resp.StatusCode)
|
|
|
|
|
|
|
|
if resp.StatusCode != 200 {
|
|
|
|
var er struct {
|
|
|
|
Error string `json:"error"`
|
|
|
|
ErrorDescription string `json:"error_description"`
|
|
|
|
}
|
|
|
|
|
|
|
|
err = json.Unmarshal(body, &er)
|
|
|
|
if err != nil {
|
|
|
|
return Token{}, fmt.Errorf("%w: %s", ErrWrongResponseFromServer, string(body))
|
|
|
|
}
|
|
|
|
if er.ErrorDescription != "" {
|
|
|
|
return Token{}, fmt.Errorf("%w: %s", ErrWrongResponseFromServer, er.ErrorDescription)
|
|
|
|
}
|
|
|
|
|
|
|
|
return Token{}, fmt.Errorf("%w: %s", ErrWrongResponseFromServer, string(body))
|
|
|
|
}
|
|
|
|
|
|
|
|
t := Token{}
|
|
|
|
t.CreatedAt = time.Now()
|
|
|
|
|
|
|
|
err = json.Unmarshal(body, &t)
|
|
|
|
if err != nil {
|
|
|
|
return Token{}, fmt.Errorf("%w: %q", ErrCantGetTokenForCode, err)
|
|
|
|
}
|
|
|
|
|
2024-09-13 09:05:36 +00:00
|
|
|
return t, nil
|
|
|
|
}
|