Initial Commit
This commit is contained in:
commit
f8859c5312
|
@ -0,0 +1,23 @@
|
|||
module cognito-auth
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2 v1.30.5
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.33
|
||||
github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.43.4
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.32 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 // indirect
|
||||
github.com/aws/smithy-go v1.20.4 // indirect
|
||||
)
|
|
@ -0,0 +1,28 @@
|
|||
github.com/aws/aws-sdk-go-v2 v1.30.5 h1:mWSRTwQAb0aLE17dSzztCVJWI9+cRMgqebndjwDyK0g=
|
||||
github.com/aws/aws-sdk-go-v2 v1.30.5/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.33 h1:Nof9o/MsmH4oa0s2q9a0k7tMz5x/Yj5k06lDODWz3BU=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.33/go.mod h1:kEqdYzRb8dd8Sy2pOdEbExTTF5v7ozEXX0McgPE7xks=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.32 h1:7Cxhp/BnT2RcGy4VisJ9miUPecY+lyE9I8JvcZofn9I=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.32/go.mod h1:P5/QMF3/DCHbXGEGkdbilXHsyTBX5D3HSwcrSc9p20I=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 h1:pfQ2sqNpMVK6xz2RbqLEL0GH87JOwSxPV2rzm8Zsb74=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13/go.mod h1:NG7RXPUlqfsCLLFfi0+IpKN4sCB9D9fw/qTaSB+xRoU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 h1:pI7Bzt0BJtYA0N/JEC6B8fJ4RBrEMi1LBrkMdFYNSnQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17/go.mod h1:Dh5zzJYMtxfIjYW+/evjQ8uj2OyR/ve2KROHGHlSFqE=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 h1:Mqr/V5gvrhA2gvgnF42Zh5iMiQNcOYthFYwCyrnuWlc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17/go.mod h1:aLJpZlCmjE+V+KtN1q1uyZkfnUWpQGpbsn89XPKyzfU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
||||
github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.43.4 h1:C8uf+nwieFWZtdPTCYOM8u/UyaIsDPfr95TJrfYekwQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.43.4/go.mod h1:hsciKQ2xFfOPEuebyKmFo7wOSVNoLuzmCi6Qtol4UDc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 h1:rfprUlsdzgl7ZL2KlXiUAoJnI/VxfHCvDFr2QDFj6u4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19/go.mod h1:SCWkEdRq8/7EK60NcvvQ6NXKuTcchAD4ROAsC37VEZE=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 h1:pIaGg+08llrP7Q5aiz9ICWbY8cqhTkyy+0SHvfzQpTc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.22.7/go.mod h1:eEygMHnTKH/3kNp9Jr1n3PdejuSNcgwLe1dWgQtO0VQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 h1:/Cfdu0XV3mONYKaOt1Gr0k1KvQzkzPyiKUdlWJqy+J4=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7/go.mod h1:bCbAxKDqNvkHxRaIMnyVPXPo+OaPRwvmgzMxbz1VKSA=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 h1:NKTa1eqZYw8tiHSRGpP0VtTdub/8KNk8sDkNPFaOKDE=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.7/go.mod h1:NXi1dIAGteSaRLqYgarlhP/Ij0cFT+qmCwiJqWh/U5o=
|
||||
github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4=
|
||||
github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
|
@ -0,0 +1,138 @@
|
|||
package cognito
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider"
|
||||
"github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider/types"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
CognitoClient *cognitoidentityprovider.Client
|
||||
poolId string
|
||||
clientId string
|
||||
}
|
||||
|
||||
func NewAuthClient(poolID string, clientId string) (AuthClient, error) {
|
||||
sdkConfig, err := config.LoadDefaultConfig(context.TODO())
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
cognitoClient := cognitoidentityprovider.NewFromConfig(sdkConfig)
|
||||
|
||||
return &Client{CognitoClient: cognitoClient, poolId: poolID, clientId: clientId}, nil
|
||||
}
|
||||
|
||||
type AuthClient interface {
|
||||
SignUp(req SignUpRequest) error
|
||||
ConfirmSignUp(req ConfirmSignUpRequest) error
|
||||
SignIn(ctx context.Context, username, password string) (*AuthenticationResult, error)
|
||||
}
|
||||
|
||||
type SignUpRequest struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
func (c *Client) SignUp(req SignUpRequest) error {
|
||||
secretHash := getSecretHash(req.Username, *&c.clientId)
|
||||
params := &cognitoidentityprovider.SignUpInput{
|
||||
ClientId: aws.String(c.clientId),
|
||||
Username: aws.String(req.Username),
|
||||
Password: aws.String(req.Password),
|
||||
UserAttributes: []types.AttributeType{
|
||||
{Name: aws.String("email"), Value: aws.String(req.Email)},
|
||||
},
|
||||
SecretHash: &secretHash,
|
||||
}
|
||||
|
||||
_, err := c.CognitoClient.SignUp(context.Background(), params)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type ConfirmSignUpRequest struct {
|
||||
Context context.Context
|
||||
Username string
|
||||
ConfirmationCode string
|
||||
}
|
||||
|
||||
func (c *Client) ConfirmSignUp(req ConfirmSignUpRequest) error {
|
||||
input := &cognitoidentityprovider.ConfirmSignUpInput{
|
||||
ClientId: aws.String(c.clientId),
|
||||
Username: aws.String(req.Username),
|
||||
ConfirmationCode: aws.String(req.ConfirmationCode),
|
||||
}
|
||||
|
||||
_, err := c.CognitoClient.ConfirmSignUp(req.Context, input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type SignInRequest struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// SignIn authenticates a user and returns tokens
|
||||
func (c *Client) SignIn(ctx context.Context, username, password string) (*AuthenticationResult, error) {
|
||||
authParams := map[string]string{
|
||||
"USERNAME": username,
|
||||
"PASSWORD": password,
|
||||
}
|
||||
|
||||
input := &cognitoidentityprovider.InitiateAuthInput{
|
||||
AuthFlow: types.AuthFlowTypeUserPasswordAuth,
|
||||
ClientId: aws.String(c.clientId),
|
||||
AuthParameters: authParams,
|
||||
}
|
||||
|
||||
output, err := c.CognitoClient.InitiateAuth(ctx, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if output.AuthenticationResult == nil {
|
||||
return nil, errors.New("authentication result is nil")
|
||||
}
|
||||
|
||||
return &AuthenticationResult{
|
||||
IDToken: aws.ToString(output.AuthenticationResult.IdToken),
|
||||
AccessToken: aws.ToString(output.AuthenticationResult.AccessToken),
|
||||
RefreshToken: aws.ToString(output.AuthenticationResult.RefreshToken),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AuthenticationResult holds the tokens returned after successful authentication
|
||||
type AuthenticationResult struct {
|
||||
IDToken string
|
||||
AccessToken string
|
||||
RefreshToken string
|
||||
}
|
||||
|
||||
func getSecretHash(username string, clientID string) string {
|
||||
secret := os.Getenv("COGNITO_SECRET")
|
||||
if secret == "" {
|
||||
secret = "1bb1r4fegke1hcn6rjo8d38io5np0qcce7juhjb8hu4kvu6qfr3s"
|
||||
}
|
||||
mac := hmac.New(sha256.New, []byte(secret))
|
||||
mac.Write([]byte(fmt.Sprintf("%s%s", username, clientID)))
|
||||
|
||||
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
package cognito
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Handlers struct {
|
||||
Client *Client
|
||||
Templates *template.Template
|
||||
}
|
||||
|
||||
func NewHandlers(client *Client, templateDir string) (*Handlers, error) {
|
||||
templates, err := template.ParseGlob(filepath.Join(templateDir, "*.html"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Handlers{
|
||||
Client: client,
|
||||
Templates: templates,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *Handlers) RenderTemplate(w http.ResponseWriter, tmpl string, data interface{}) {
|
||||
err := h.Templates.ExecuteTemplate(w, tmpl+".html", data)
|
||||
if err != nil {
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
type JSONResponse struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (h *Handlers) SignUpHTMLHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
// Render the sign-up form
|
||||
h.RenderTemplate(w, "signup", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == http.MethodPost {
|
||||
// Parse form data
|
||||
if err := r.ParseForm(); err != nil {
|
||||
h.RenderTemplate(w, "signup", map[string]string{"Error": "Invalid form data"})
|
||||
return
|
||||
}
|
||||
|
||||
username := r.FormValue("username")
|
||||
email := r.FormValue("email")
|
||||
password := r.FormValue("password")
|
||||
|
||||
// Sign up the user
|
||||
err := h.Client.SignUp(SignUpRequest{
|
||||
Context: r.Context(),
|
||||
Username: username,
|
||||
Password: password,
|
||||
Email: email,
|
||||
})
|
||||
if err != nil {
|
||||
h.RenderTemplate(w, "signup", map[string]string{"Error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Redirect to confirmation page or show success message
|
||||
http.Redirect(w, r, "/confirm_signup", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
func (h *Handlers) SignUpJSONHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
if err := decoder.Decode(&req); err != nil {
|
||||
http.Error(w, "Bad Request: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err := h.Client.SignUp(SignUpRequest{
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Email: req.Email,
|
||||
Context: r.Context(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
response := JSONResponse{Error: err.Error()}
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
return
|
||||
}
|
||||
|
||||
response := JSONResponse{Message: "User signed up successfully. Please confirm your email."}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
func (h *Handlers) ConfirmSignUpHTMLHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
// Render the confirm sign-up form
|
||||
h.RenderTemplate(w, "confirm_signup", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == http.MethodPost {
|
||||
// Parse form data
|
||||
if err := r.ParseForm(); err != nil {
|
||||
h.RenderTemplate(w, "confirm_signup", map[string]string{"Error": "Invalid form data"})
|
||||
return
|
||||
}
|
||||
|
||||
username := r.FormValue("username")
|
||||
confirmationCode := r.FormValue("confirmation_code")
|
||||
|
||||
// Confirm sign-up
|
||||
err := h.Client.ConfirmSignUp(ConfirmSignUpRequest{
|
||||
Context: r.Context(),
|
||||
Username: username,
|
||||
ConfirmationCode: confirmationCode,
|
||||
})
|
||||
if err != nil {
|
||||
h.RenderTemplate(w, "confirm_signup", map[string]string{"Error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Redirect to sign-in page or show success message
|
||||
http.Redirect(w, r, "/signin", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
func (h *Handlers) ConfirmSignUpJSONHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Username string `json:"username"`
|
||||
ConfirmationCode string `json:"confirmation_code"`
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
if err := decoder.Decode(&req); err != nil {
|
||||
http.Error(w, "Bad Request: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err := h.Client.ConfirmSignUp(ConfirmSignUpRequest{
|
||||
Context: r.Context(),
|
||||
Username: req.Username,
|
||||
ConfirmationCode: req.ConfirmationCode,
|
||||
})
|
||||
if err != nil {
|
||||
response := JSONResponse{Error: err.Error()}
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
return
|
||||
}
|
||||
|
||||
response := JSONResponse{Message: "User confirmed successfully."}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// SignInHTMLHandler handles HTML sign-in requests
|
||||
func (h *Handlers) SignInHTMLHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
// Render the sign-in form
|
||||
h.RenderTemplate(w, "signin", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == http.MethodPost {
|
||||
// Parse form data
|
||||
if err := r.ParseForm(); err != nil {
|
||||
h.RenderTemplate(w, "signin", map[string]string{"Error": "Invalid form data"})
|
||||
return
|
||||
}
|
||||
|
||||
username := r.FormValue("username")
|
||||
password := r.FormValue("password")
|
||||
|
||||
// Sign in the user
|
||||
authResult, err := h.Client.SignIn(r.Context(), username, password)
|
||||
if err != nil {
|
||||
h.RenderTemplate(w, "signin", map[string]string{"Error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Render a success page or display tokens
|
||||
h.RenderTemplate(w, "signin_success", authResult)
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
func (h *Handlers) SignInJSONHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
if err := decoder.Decode(&req); err != nil {
|
||||
http.Error(w, "Bad Request: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
authResult, err := h.Client.SignIn(r.Context(), req.Username, req.Password)
|
||||
if err != nil {
|
||||
response := JSONResponse{Error: err.Error()}
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
return
|
||||
}
|
||||
|
||||
response := JSONResponse{Data: authResult}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
Loading…
Reference in New Issue