✨ OAuth2 Support
This commit is contained in:
@ -7,16 +7,20 @@ import (
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/spf13/viper"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
func getPrincipal(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
|
||||
var data models.Account
|
||||
if err := database.C.Where(&models.Account{
|
||||
BaseModel: models.BaseModel{ID: user.ID},
|
||||
}).Preload(clause.Associations).First(&data).Error; err != nil {
|
||||
if err := database.C.
|
||||
Where(&models.Account{BaseModel: models.BaseModel{ID: user.ID}}).
|
||||
Preload("Profile").
|
||||
Preload("Contacts").
|
||||
Preload("Factors").
|
||||
Preload("Sessions").
|
||||
Preload("Challenges").
|
||||
First(&data).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,7 @@ func doChallenge(c *fiber.Ctx) error {
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else if challenge.Progress >= challenge.Requirements {
|
||||
session, err := security.GrantSession(challenge, []string{"*"}, nil, lo.ToPtr(time.Now()))
|
||||
session, err := security.GrantSession(challenge, []string{"*"}, []string{"Hydrogen.Passport"}, nil, lo.ToPtr(time.Now()))
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
@ -89,8 +89,11 @@ func doChallenge(c *fiber.Ctx) error {
|
||||
|
||||
func exchangeToken(c *fiber.Ctx) error {
|
||||
var data struct {
|
||||
Code string `json:"code"`
|
||||
GrantType string `json:"grant_type"`
|
||||
Code string `json:"code" form:"code"`
|
||||
ClientID string `json:"client_id" form:"client_id"`
|
||||
ClientSecret string `json:"client_secret" form:"client_secret"`
|
||||
RedirectUri string `json:"redirect_uri" form:"redirect_uri"`
|
||||
GrantType string `json:"grant_type" form:"grant_type"`
|
||||
}
|
||||
|
||||
if err := BindAndValidate(c, &data); err != nil {
|
||||
@ -99,6 +102,18 @@ func exchangeToken(c *fiber.Ctx) error {
|
||||
|
||||
switch data.GrantType {
|
||||
case "authorization_code":
|
||||
// Authorization Code Mode
|
||||
access, refresh, err := security.ExchangeOauthToken(data.ClientID, data.ClientSecret, data.RedirectUri, data.Code)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"access_token": access,
|
||||
"refresh_token": refresh,
|
||||
})
|
||||
case "grant_token":
|
||||
// Internal Usage
|
||||
access, refresh, err := security.ExchangeToken(data.Code)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
@ -109,6 +124,7 @@ func exchangeToken(c *fiber.Ctx) error {
|
||||
"refresh_token": refresh,
|
||||
})
|
||||
case "refresh_token":
|
||||
// Refresh Token
|
||||
access, refresh, err := security.RefreshToken(data.Code)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
@ -119,6 +135,6 @@ func exchangeToken(c *fiber.Ctx) error {
|
||||
"refresh_token": refresh,
|
||||
})
|
||||
default:
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Unsupported exchange token type.")
|
||||
return fiber.NewError(fiber.StatusBadRequest, "unsupported exchange token type")
|
||||
}
|
||||
}
|
||||
|
119
pkg/server/oauth_api.go
Normal file
119
pkg/server/oauth_api.go
Normal file
@ -0,0 +1,119 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"code.smartsheep.studio/hydrogen/passport/pkg/database"
|
||||
"code.smartsheep.studio/hydrogen/passport/pkg/models"
|
||||
"code.smartsheep.studio/hydrogen/passport/pkg/security"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func preConnect(c *fiber.Ctx) error {
|
||||
id := c.Query("client_id")
|
||||
redirect := c.Query("redirect_uri")
|
||||
|
||||
var client models.ThirdClient
|
||||
if err := database.C.Where(&models.ThirdClient{Alias: id}).First(&client).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else if !client.IsDraft && !lo.Contains(client.Callbacks, strings.Split(redirect, "?")[0]) {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "invalid request url")
|
||||
}
|
||||
|
||||
user := c.Locals("principal").(models.Account)
|
||||
|
||||
var session models.AuthSession
|
||||
if err := database.C.Where(&models.AuthSession{
|
||||
AccountID: user.ID,
|
||||
ClientID: &client.ID,
|
||||
}).First(&session).Error; err == nil {
|
||||
if session.ExpiredAt != nil && session.ExpiredAt.Unix() < time.Now().Unix() {
|
||||
return c.JSON(fiber.Map{
|
||||
"client": client,
|
||||
"session": nil,
|
||||
})
|
||||
} else {
|
||||
session, err = security.RegenSession(session)
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"client": client,
|
||||
"session": session,
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"client": client,
|
||||
"session": nil,
|
||||
})
|
||||
}
|
||||
|
||||
func doConnect(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
id := c.Query("client_id")
|
||||
response := c.Query("response_type")
|
||||
redirect := c.Query("redirect_uri")
|
||||
scope := c.Query("scope")
|
||||
if len(scope) <= 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "invalid request params")
|
||||
}
|
||||
|
||||
var client models.ThirdClient
|
||||
if err := database.C.Where(&models.ThirdClient{Alias: id}).First(&client).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
switch response {
|
||||
case "code":
|
||||
// OAuth Authorization Mode
|
||||
expired := time.Now().Add(7 * 24 * time.Hour)
|
||||
session, err := security.GrantOauthSession(
|
||||
user,
|
||||
client,
|
||||
strings.Split(scope, " "),
|
||||
[]string{"Hydrogen.Passport", client.Alias},
|
||||
&expired,
|
||||
lo.ToPtr(time.Now()),
|
||||
c.IP(),
|
||||
c.Get(fiber.HeaderUserAgent),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else {
|
||||
return c.JSON(fiber.Map{
|
||||
"session": session,
|
||||
"redirect_uri": redirect,
|
||||
})
|
||||
}
|
||||
case "token":
|
||||
// OAuth Implicit Mode
|
||||
expired := time.Now().Add(7 * 24 * time.Hour)
|
||||
session, err := security.GrantOauthSession(
|
||||
user,
|
||||
client,
|
||||
strings.Split(scope, " "),
|
||||
[]string{"Hydrogen.Passport", client.Alias},
|
||||
&expired,
|
||||
lo.ToPtr(time.Now()),
|
||||
c.IP(),
|
||||
c.Get(fiber.HeaderUserAgent),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else if access, refresh, err := security.GetToken(session); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else {
|
||||
return c.JSON(fiber.Map{
|
||||
"access_token": access,
|
||||
"refresh_token": refresh,
|
||||
"redirect_uri": redirect,
|
||||
"session": session,
|
||||
})
|
||||
}
|
||||
default:
|
||||
return fiber.NewError(fiber.StatusBadRequest, "unsupported response type")
|
||||
}
|
||||
}
|
@ -31,6 +31,9 @@ func NewServer() {
|
||||
api.Post("/auth", doChallenge)
|
||||
api.Post("/auth/token", exchangeToken)
|
||||
api.Post("/auth/factors/:factorId", requestFactorToken)
|
||||
|
||||
api.Get("/auth/oauth/connect", auth, preConnect)
|
||||
api.Post("/auth/oauth/connect", auth, doConnect)
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user