2024-06-22 05:04:21 +00:00
|
|
|
package api
|
2024-04-20 11:04:33 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
2024-07-11 10:34:05 +00:00
|
|
|
"git.solsynth.dev/hydrogen/passport/pkg/internal/server/exts"
|
|
|
|
|
2024-04-20 11:04:33 +00:00
|
|
|
"github.com/gofiber/fiber/v2"
|
|
|
|
|
2024-06-17 14:21:34 +00:00
|
|
|
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
|
2024-04-20 11:04:33 +00:00
|
|
|
)
|
|
|
|
|
2024-06-26 10:18:04 +00:00
|
|
|
func getTicket(c *fiber.Ctx) error {
|
|
|
|
ticketId, err := c.ParamsInt("ticketId")
|
|
|
|
if err != nil {
|
|
|
|
return fiber.NewError(fiber.StatusBadRequest, "ticket id is required")
|
|
|
|
}
|
|
|
|
|
|
|
|
ticket, err := services.GetTicket(uint(ticketId))
|
|
|
|
if err != nil {
|
|
|
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("ticket %d not found", ticketId))
|
|
|
|
} else {
|
|
|
|
return c.JSON(ticket)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-20 11:04:33 +00:00
|
|
|
func doAuthenticate(c *fiber.Ctx) error {
|
|
|
|
var data struct {
|
2024-06-26 10:07:07 +00:00
|
|
|
Username string `json:"username" validate:"required"`
|
2024-04-20 11:04:33 +00:00
|
|
|
Password string `json:"password" validate:"required"`
|
|
|
|
}
|
|
|
|
|
2024-06-22 05:04:21 +00:00
|
|
|
if err := exts.BindAndValidate(c, &data); err != nil {
|
2024-04-20 11:04:33 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
user, err := services.LookupAccount(data.Username)
|
|
|
|
if err != nil {
|
|
|
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("account was not found: %v", err.Error()))
|
2024-06-26 07:52:58 +00:00
|
|
|
} else if user.ConfirmedAt == nil {
|
|
|
|
return fiber.NewError(fiber.StatusForbidden, "account was not confirmed")
|
2024-07-11 16:35:45 +00:00
|
|
|
} else if user.SuspendedAt != nil {
|
2024-07-11 10:34:05 +00:00
|
|
|
return fiber.NewError(fiber.StatusForbidden, "account was suspended")
|
2024-04-20 11:04:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ticket, err := services.NewTicket(user, c.IP(), c.Get(fiber.HeaderUserAgent))
|
|
|
|
if err != nil {
|
|
|
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable setup ticket: %v", err.Error()))
|
|
|
|
}
|
|
|
|
|
|
|
|
ticket, err = services.ActiveTicketWithPassword(ticket, data.Password)
|
|
|
|
if err != nil {
|
2024-06-26 10:07:07 +00:00
|
|
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("failed to authenticate: %v", err.Error()))
|
2024-04-20 11:04:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return c.JSON(fiber.Map{
|
2024-06-24 15:54:45 +00:00
|
|
|
"is_finished": ticket.IsAvailable() == nil,
|
2024-04-20 11:04:33 +00:00
|
|
|
"ticket": ticket,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func doMultiFactorAuthenticate(c *fiber.Ctx) error {
|
|
|
|
var data struct {
|
|
|
|
TicketID uint `json:"ticket_id" validate:"required"`
|
|
|
|
FactorID uint `json:"factor_id" validate:"required"`
|
|
|
|
Code string `json:"code" validate:"required"`
|
|
|
|
}
|
|
|
|
|
2024-06-22 05:04:21 +00:00
|
|
|
if err := exts.BindAndValidate(c, &data); err != nil {
|
2024-04-20 11:04:33 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ticket, err := services.GetTicket(data.TicketID)
|
|
|
|
if err != nil {
|
|
|
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("ticket was not found: %v", err.Error()))
|
|
|
|
}
|
|
|
|
|
|
|
|
factor, err := services.GetFactor(data.FactorID)
|
|
|
|
if err != nil {
|
|
|
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("factor was not found: %v", err.Error()))
|
|
|
|
}
|
|
|
|
|
|
|
|
ticket, err = services.ActiveTicketWithMFA(ticket, factor, data.Code)
|
|
|
|
if err != nil {
|
2024-06-26 10:07:07 +00:00
|
|
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("failed to authenticate: %v", err.Error()))
|
2024-04-20 11:04:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return c.JSON(fiber.Map{
|
2024-06-24 15:54:45 +00:00
|
|
|
"is_finished": ticket.IsAvailable() == nil,
|
2024-04-20 11:04:33 +00:00
|
|
|
"ticket": ticket,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func getToken(c *fiber.Ctx) error {
|
|
|
|
var data struct {
|
|
|
|
Code string `json:"code" form:"code"`
|
|
|
|
RefreshToken string `json:"refresh_token" form:"refresh_token"`
|
|
|
|
ClientID string `json:"client_id" form:"client_id"`
|
|
|
|
ClientSecret string `json:"client_secret" form:"client_secret"`
|
|
|
|
Username string `json:"username" form:"username"`
|
|
|
|
Password string `json:"password" form:"password"`
|
|
|
|
RedirectUri string `json:"redirect_uri" form:"redirect_uri"`
|
|
|
|
GrantType string `json:"grant_type" form:"grant_type"`
|
|
|
|
}
|
|
|
|
|
2024-06-22 05:04:21 +00:00
|
|
|
if err := exts.BindAndValidate(c, &data); err != nil {
|
2024-04-20 11:04:33 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
2024-07-28 12:04:22 +00:00
|
|
|
var idk, atk, rtk string
|
2024-04-20 11:04:33 +00:00
|
|
|
switch data.GrantType {
|
|
|
|
case "refresh_token":
|
|
|
|
// Refresh Token
|
2024-07-28 12:04:22 +00:00
|
|
|
atk, rtk, err = services.RefreshToken(data.RefreshToken)
|
2024-04-20 11:04:33 +00:00
|
|
|
if err != nil {
|
|
|
|
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
|
|
|
}
|
|
|
|
case "authorization_code":
|
|
|
|
// Authorization Code Mode
|
2024-07-28 12:04:22 +00:00
|
|
|
idk, atk, rtk, err = services.ExchangeOauthToken(data.ClientID, data.ClientSecret, data.RedirectUri, data.Code)
|
2024-04-20 11:04:33 +00:00
|
|
|
if err != nil {
|
|
|
|
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
|
|
|
}
|
|
|
|
case "password":
|
|
|
|
// Password Mode
|
|
|
|
user, err := services.LookupAccount(data.Username)
|
|
|
|
if err != nil {
|
|
|
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("account was not found: %v", err.Error()))
|
|
|
|
}
|
|
|
|
ticket, err := services.NewTicket(user, c.IP(), c.Get(fiber.HeaderUserAgent))
|
|
|
|
if err != nil {
|
|
|
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable setup ticket: %v", err.Error()))
|
|
|
|
}
|
|
|
|
ticket, err = services.ActiveTicketWithPassword(ticket, data.Password)
|
|
|
|
if err != nil {
|
|
|
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("invalid password: %v", err.Error()))
|
2024-04-21 13:36:18 +00:00
|
|
|
} else if err := ticket.IsAvailable(); err != nil {
|
2024-04-30 17:33:11 +00:00
|
|
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("risk detected: %v (ticketId=%d)", err, ticket.ID))
|
2024-04-20 11:04:33 +00:00
|
|
|
}
|
2024-07-28 12:04:22 +00:00
|
|
|
idk, atk, rtk, err = services.ExchangeOauthToken(data.ClientID, data.ClientSecret, data.RedirectUri, *ticket.GrantToken)
|
2024-04-20 11:04:33 +00:00
|
|
|
if err != nil {
|
|
|
|
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
|
|
|
}
|
|
|
|
case "grant_token":
|
|
|
|
// Internal Usage
|
2024-07-28 12:04:22 +00:00
|
|
|
atk, rtk, err = services.ExchangeToken(data.Code)
|
2024-04-20 11:04:33 +00:00
|
|
|
if err != nil {
|
|
|
|
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return fiber.NewError(fiber.StatusBadRequest, "unsupported exchange token type")
|
|
|
|
}
|
|
|
|
|
2024-07-28 12:04:22 +00:00
|
|
|
if len(idk) == 0 {
|
|
|
|
idk = atk
|
|
|
|
}
|
|
|
|
|
|
|
|
exts.SetAuthCookies(c, atk, rtk)
|
2024-04-20 11:04:33 +00:00
|
|
|
|
|
|
|
return c.JSON(fiber.Map{
|
2024-07-28 12:04:22 +00:00
|
|
|
"id_token": idk,
|
|
|
|
"access_token": atk,
|
|
|
|
"refresh_token": rtk,
|
2024-04-20 11:04:33 +00:00
|
|
|
"token_type": "Bearer",
|
|
|
|
"expires_in": (30 * time.Minute).Seconds(),
|
|
|
|
})
|
|
|
|
}
|