Add Identity Provider to support Authentication via Authentik,OpenID ,etc..

This commit is contained in:
Mo Tarbin 2025-02-04 23:59:49 -05:00
parent 0647725c68
commit 430f46ffee
10 changed files with 269 additions and 30 deletions

View file

@ -31,16 +31,18 @@ type Handler struct {
circleRepo *cRepo.CircleRepository
jwtAuth *jwt.GinJWTMiddleware
email *email.EmailSender
identityProvider *auth.IdentityProvider
isDonetickDotCom bool
IsUserCreationDisabled bool
}
func NewHandler(ur *uRepo.UserRepository, cr *cRepo.CircleRepository, jwtAuth *jwt.GinJWTMiddleware, email *email.EmailSender, config *config.Config) *Handler {
func NewHandler(ur *uRepo.UserRepository, cr *cRepo.CircleRepository, jwtAuth *jwt.GinJWTMiddleware, email *email.EmailSender, idp *auth.IdentityProvider, config *config.Config) *Handler {
return &Handler{
userRepo: ur,
circleRepo: cr,
jwtAuth: jwtAuth,
email: email,
identityProvider: idp,
isDonetickDotCom: config.IsDoneTickDotCom,
IsUserCreationDisabled: config.IsUserCreationDisabled,
}
@ -178,7 +180,8 @@ func (h *Handler) thirdPartyAuthCallback(c *gin.Context) {
provider := c.Param("provider")
logger.Infow("account.handler.thirdPartyAuthCallback", "provider", provider)
if provider == "google" {
switch provider {
case "google":
c.Set("auth_provider", "3rdPartyAuth")
type OAuthRequest struct {
Token string `json:"token" binding:"required"`
@ -219,7 +222,7 @@ func (h *Handler) thirdPartyAuthCallback(c *gin.Context) {
Image: userinfo.Picture,
Password: encodedPassword,
DisplayName: userinfo.GivenName,
Provider: 2,
Provider: uModel.AuthProviderGoogle,
}
createdUser, err := h.userRepo.CreateUser(c, acc)
if err != nil {
@ -278,6 +281,106 @@ func (h *Handler) thirdPartyAuthCallback(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{"token": tokenString, "expire": expire})
return
case "oauth2":
c.Set("auth_provider", "3rdPartyAuth")
// Read the ID token from the request bod
type Request struct {
Code string `json:"code"`
}
var req Request
if err := c.BindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
return
}
token, err := h.identityProvider.ExchangeToken(c, req.Code)
if err != nil {
logger.Errorw("account.handler.thirdPartyAuthCallback (oauth2) failed to exchange token", "err", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to exchange token"})
return
}
claims, err := h.identityProvider.GetUserInfo(c, token)
if err != nil {
logger.Errorw("account.handler.thirdPartyAuthCallback (oauth2) failed to get claims", "err", err)
}
acc, err := h.userRepo.FindByEmail(c, claims.Email)
if err != nil {
// Create user
password := auth.GenerateRandomPassword(12)
encodedPassword, err := auth.EncodePassword(password)
if err != nil {
logger.Errorw("account.handler.thirdPartyAuthCallback (oauth2) password encoding failed", "err", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Password encoding failed"})
return
}
acc = &uModel.User{
Username: claims.Email,
Email: claims.Email,
Password: encodedPassword,
DisplayName: claims.DisplayName,
Provider: uModel.AuthProviderOAuth2,
}
createdUser, err := h.userRepo.CreateUser(c, acc)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Unable to create user",
})
return
}
// Create Circle for the user:
userCircle, err := h.circleRepo.CreateCircle(c, &cModel.Circle{
Name: claims.DisplayName + "'s circle",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
InviteCode: utils.GenerateInviteCode(c),
})
if err != nil {
c.JSON(500, gin.H{
"error": "Error creating circle",
})
return
}
if err := h.circleRepo.AddUserToCircle(c, &cModel.UserCircle{
UserID: createdUser.ID,
CircleID: userCircle.ID,
Role: "admin",
IsActive: true,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}); err != nil {
c.JSON(500, gin.H{
"error": "Error adding user to circle",
})
return
}
createdUser.CircleID = userCircle.ID
if err := h.userRepo.UpdateUser(c, createdUser); err != nil {
c.JSON(500, gin.H{
"error": "Error updating user",
})
return
}
}
// ... (JWT generation and response)
c.Set("user_account", acc)
h.jwtAuth.Authenticator(c)
tokenString, expire, err := h.jwtAuth.TokenGenerator(acc)
if err != nil {
logger.Errorw("Unable to Generate a Token")
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Unable to Generate a Token",
})
return
}
c.JSON(http.StatusOK, gin.H{"token": tokenString, "expire": expire})
return
default:
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid provider"})
return
}
}

View file

@ -7,18 +7,18 @@ import (
)
type User struct {
ID int `json:"id" gorm:"primary_key"` // Unique identifier
DisplayName string `json:"displayName" gorm:"column:display_name"` // Display name
Username string `json:"username" gorm:"column:username;unique"` // Username (unique)
Email string `json:"email" gorm:"column:email;unique"` // Email (unique)
Provider int `json:"provider" gorm:"column:provider"` // Provider
Password string `json:"-" gorm:"column:password"` // Password
CircleID int `json:"circleID" gorm:"column:circle_id"` // Circle ID
ChatID int64 `json:"chatID" gorm:"column:chat_id"` // Telegram chat ID
Image string `json:"image" gorm:"column:image"` // Image
CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` // Created at
UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"` // Updated at
Disabled bool `json:"disabled" gorm:"column:disabled"` // Disabled
ID int `json:"id" gorm:"primary_key"` // Unique identifier
DisplayName string `json:"displayName" gorm:"column:display_name"` // Display name
Username string `json:"username" gorm:"column:username;unique"` // Username (unique)
Email string `json:"email" gorm:"column:email;unique"` // Email (unique)
Provider AuthProvider `json:"provider" gorm:"column:provider"` // Provider
Password string `json:"-" gorm:"column:password"` // Password
CircleID int `json:"circleID" gorm:"column:circle_id"` // Circle ID
ChatID int64 `json:"chatID" gorm:"column:chat_id"` // Telegram chat ID
Image string `json:"image" gorm:"column:image"` // Image
CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` // Created at
UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"` // Updated at
Disabled bool `json:"disabled" gorm:"column:disabled"` // Disabled
// Email string `json:"email" gorm:"column:email"` // Email
CustomerID *string `gorm:"column:customer_id;<-:false"` // read only column
Subscription *string `json:"subscription" gorm:"column:subscription;<-:false"` // read only column
@ -48,3 +48,10 @@ type UserNotificationTarget struct {
TargetID string `json:"target_id" gorm:"column:target_id"` // Target ID
CreatedAt time.Time `json:"-" gorm:"column:created_at"`
}
type AuthProvider int
const (
AuthProviderDonetick AuthProvider = iota
AuthProviderOAuth2
AuthProviderGoogle
)