🗑️ Clean up code
This commit is contained in:
282
pkg/internal/web/api/accounts_api.go
Normal file
282
pkg/internal/web/api/accounts_api.go
Normal file
@ -0,0 +1,282 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func getUserInBatch(c *fiber.Ctx) error {
|
||||
id := c.Query("id")
|
||||
list := strings.Split(id, ",")
|
||||
var nameList []string
|
||||
numericList := lo.Filter(lo.Map(list, func(str string, i int) int {
|
||||
value, err := strconv.Atoi(str)
|
||||
if err != nil {
|
||||
nameList = append(nameList, str)
|
||||
return 0
|
||||
}
|
||||
return value
|
||||
}), func(vak int, idx int) bool {
|
||||
return vak > 0
|
||||
})
|
||||
|
||||
tx := database.C
|
||||
if len(numericList) > 0 {
|
||||
tx = tx.Where("id IN ?", numericList)
|
||||
}
|
||||
if len(nameList) > 0 {
|
||||
tx = tx.Or("name IN ?", nameList)
|
||||
}
|
||||
if len(nameList) == 0 && len(numericList) == 0 {
|
||||
return c.JSON([]models.Account{})
|
||||
}
|
||||
|
||||
var accounts []models.Account
|
||||
if err := tx.
|
||||
Preload("Profile").
|
||||
Preload("Badges", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Order("badges.type DESC")
|
||||
}).
|
||||
Find(&accounts).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(accounts)
|
||||
}
|
||||
|
||||
func lookupAccount(c *fiber.Ctx) error {
|
||||
probe := c.Query("probe")
|
||||
if len(probe) == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "lookup probe is required")
|
||||
}
|
||||
|
||||
user, err := services.LookupAccount(probe)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(user)
|
||||
}
|
||||
|
||||
func searchAccount(c *fiber.Ctx) error {
|
||||
probe := c.Query("probe")
|
||||
if len(probe) == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "search probe is required")
|
||||
}
|
||||
|
||||
users, err := services.SearchAccount(probe)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(users)
|
||||
}
|
||||
|
||||
func getUserinfo(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var data models.Account
|
||||
if err := database.C.
|
||||
Where(&models.Account{BaseModel: models.BaseModel{ID: user.ID}}).
|
||||
Preload("Profile").
|
||||
Preload("Contacts").
|
||||
Preload("Badges").
|
||||
First(&data).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else {
|
||||
data.PermNodes = c.Locals("nex_user").(*sec.UserInfo).PermNodes
|
||||
}
|
||||
|
||||
var resp fiber.Map
|
||||
raw, _ := jsoniter.Marshal(data)
|
||||
_ = jsoniter.Unmarshal(raw, &resp)
|
||||
|
||||
return c.JSON(resp)
|
||||
}
|
||||
|
||||
func updateUserinfo(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var data struct {
|
||||
Nick string `json:"nick" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Birthday time.Time `json:"birthday"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
} else {
|
||||
data.Nick = strings.TrimSpace(data.Nick)
|
||||
}
|
||||
if !services.ValidateAccountName(data.Nick, 4, 24) {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "invalid account nick, length requires 4 to 24")
|
||||
}
|
||||
|
||||
var account models.Account
|
||||
if err := database.C.
|
||||
Where(&models.Account{BaseModel: models.BaseModel{ID: user.ID}}).
|
||||
Preload("Profile").
|
||||
First(&account).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
account.Nick = data.Nick
|
||||
account.Description = data.Description
|
||||
account.Profile.FirstName = data.FirstName
|
||||
account.Profile.LastName = data.LastName
|
||||
account.Profile.Birthday = &data.Birthday
|
||||
|
||||
if err := database.C.Save(&account).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else if err := database.C.Save(&account.Profile).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
services.AddEvent(user.ID, "profile.edit", strconv.Itoa(int(user.ID)), c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
services.InvalidAuthCacheWithUser(account.ID)
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
func updateAccountLanguage(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var data struct {
|
||||
Language string `json:"language" validate:"required,bcp47_language_tag"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := database.C.Model(&models.Account{}).Where("id = ?", user.ID).
|
||||
Updates(&models.Account{Language: data.Language}).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
services.AddEvent(user.ID, "profile.edit.language", strconv.Itoa(int(user.ID)), c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
services.InvalidAuthCacheWithUser(user.ID)
|
||||
|
||||
user.Language = data.Language
|
||||
|
||||
return c.JSON(user)
|
||||
}
|
||||
|
||||
func doRegister(c *fiber.Ctx) error {
|
||||
var data struct {
|
||||
Name string `json:"name" validate:"required,lowercase,alphanum,min=4,max=16"`
|
||||
Nick string `json:"nick" validate:"required"`
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
Password string `json:"password" validate:"required,min=4,max=32"`
|
||||
Language string `json:"language" validate:"required,bcp47_language_tag"`
|
||||
MagicToken string `json:"magic_token"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
} else {
|
||||
data.Name = strings.TrimSpace(data.Name)
|
||||
data.Nick = strings.TrimSpace(data.Nick)
|
||||
data.Email = strings.TrimSpace(data.Email)
|
||||
}
|
||||
if _, err := strconv.Atoi(data.Name); err == nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "invalid account name, cannot be pure number")
|
||||
}
|
||||
if !services.ValidateAccountName(data.Nick, 4, 24) {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "invalid account nick, length requires 4 to 24")
|
||||
}
|
||||
if viper.GetBool("use_registration_magic_token") && len(data.MagicToken) <= 0 {
|
||||
return fmt.Errorf("missing magic token in request")
|
||||
} else if viper.GetBool("use_registration_magic_token") {
|
||||
if tk, err := services.ValidateMagicToken(data.MagicToken, models.RegistrationMagicToken); err != nil {
|
||||
return err
|
||||
} else {
|
||||
database.C.Delete(&tk)
|
||||
}
|
||||
}
|
||||
|
||||
if user, err := services.CreateAccount(
|
||||
data.Name,
|
||||
data.Nick,
|
||||
data.Email,
|
||||
data.Password,
|
||||
data.Language,
|
||||
); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.JSON(user)
|
||||
}
|
||||
}
|
||||
|
||||
func doRegisterConfirm(c *fiber.Ctx) error {
|
||||
var data struct {
|
||||
Code string `json:"code" validate:"required"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := services.ConfirmAccount(data.Code); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
func requestDeleteAccount(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
if err := services.CheckAbleToDeleteAccount(user); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else if err = services.RequestDeleteAccount(user); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
func confirmDeleteAccount(c *fiber.Ctx) error {
|
||||
var data struct {
|
||||
Code string `json:"code" validate:"required"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := services.ConfirmDeleteAccount(data.Code); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
159
pkg/internal/web/api/auth_api.go
Normal file
159
pkg/internal/web/api/auth_api.go
Normal file
@ -0,0 +1,159 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func doAuthenticate(c *fiber.Ctx) error {
|
||||
var data struct {
|
||||
Username string `json:"username" validate:"required"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
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()))
|
||||
} else if user.SuspendedAt != nil {
|
||||
return fiber.NewError(fiber.StatusForbidden, "account was suspended")
|
||||
}
|
||||
|
||||
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()))
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"is_finished": ticket.IsAvailable() == nil,
|
||||
"ticket": ticket,
|
||||
})
|
||||
}
|
||||
|
||||
func doAuthTicketCheck(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"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
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.PerformTicketCheck(ticket, factor, data.Code)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("failed to authenticate: %v", err.Error()))
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"is_finished": ticket.IsAvailable() == nil,
|
||||
"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"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
var idk, atk, rtk string
|
||||
switch data.GrantType {
|
||||
case "refresh_token":
|
||||
// Refresh Token
|
||||
atk, rtk, err = services.RefreshToken(data.RefreshToken)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
case "authorization_code":
|
||||
// Authorization Code Mode
|
||||
idk, atk, rtk, err = services.ExchangeOauthToken(data.ClientID, data.ClientSecret, data.RedirectUri, data.Code)
|
||||
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()))
|
||||
} else if err := ticket.IsAvailable(); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("risk detected: %v (ticketId=%d)", err, ticket.ID))
|
||||
}
|
||||
idk, atk, rtk, err = services.ExchangeOauthToken(data.ClientID, data.ClientSecret, data.RedirectUri, *ticket.GrantToken)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
case "grant_token":
|
||||
// Internal Usage
|
||||
atk, rtk, err = services.ExchangeToken(data.Code)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
default:
|
||||
return fiber.NewError(fiber.StatusBadRequest, "unsupported exchange token type")
|
||||
}
|
||||
|
||||
if len(idk) == 0 {
|
||||
idk = atk
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"id_token": idk,
|
||||
"access_token": atk,
|
||||
"refresh_token": rtk,
|
||||
"token_type": "Bearer",
|
||||
"expires_in": (30 * time.Minute).Seconds(),
|
||||
})
|
||||
}
|
88
pkg/internal/web/api/avatar_api.go
Normal file
88
pkg/internal/web/api/avatar_api.go
Normal file
@ -0,0 +1,88 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func setAvatar(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var data struct {
|
||||
AttachmentID string `json:"attachment" validate:"required"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user.Avatar = &data.AttachmentID
|
||||
|
||||
if err := database.C.Save(&user).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "profile.edit.avatar", strconv.Itoa(int(user.ID)), c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
services.InvalidAuthCacheWithUser(user.ID)
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
func setBanner(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var data struct {
|
||||
AttachmentID string `json:"attachment" validate:"required"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user.Banner = &data.AttachmentID
|
||||
|
||||
if err := database.C.Save(&user).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "profile.edit.banner", strconv.Itoa(int(user.ID)), c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
services.InvalidAuthCacheWithUser(user.ID)
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
func getAvatar(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
if content := user.GetAvatar(); content == nil {
|
||||
return c.SendStatus(fiber.StatusNotFound)
|
||||
} else {
|
||||
return c.Redirect(*content, fiber.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func getBanner(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
if content := user.GetBanner(); content == nil {
|
||||
return c.SendStatus(fiber.StatusNotFound)
|
||||
} else {
|
||||
return c.Redirect(*content, fiber.StatusFound)
|
||||
}
|
||||
}
|
219
pkg/internal/web/api/bot_token_api.go
Normal file
219
pkg/internal/web/api/bot_token_api.go
Normal file
@ -0,0 +1,219 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func listBotKeys(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var tx *gorm.DB
|
||||
|
||||
botId, _ := c.ParamsInt("botId", 0)
|
||||
if botId > 0 {
|
||||
var bot models.Account
|
||||
if err := database.C.Where("automated_id = ? AND id = ?", user.ID, botId).First(&bot).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("bot not found: %v", err))
|
||||
}
|
||||
tx = database.C.Where("account_id = ?", bot.ID)
|
||||
} else {
|
||||
tx = database.C.Where("account_id = ?", user.ID)
|
||||
}
|
||||
|
||||
countTx := tx
|
||||
var count int64
|
||||
if err := countTx.Model(&models.ApiKey{}).Count(&count).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
var keys []models.ApiKey
|
||||
if err := tx.Preload("Ticket").Find(&keys).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"count": count,
|
||||
"data": keys,
|
||||
})
|
||||
}
|
||||
|
||||
func getBotKey(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
id, _ := c.ParamsInt("id", 0)
|
||||
|
||||
var key models.ApiKey
|
||||
if err := database.C.
|
||||
Where("id = ? AND account_id = ?", id, user.ID).
|
||||
Preload("Ticket").
|
||||
First(&key).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(key)
|
||||
}
|
||||
|
||||
func createBotKey(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var data struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
Lifecycle *int64 `json:"lifecycle"`
|
||||
Claims []string `json:"claims"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
target := user
|
||||
|
||||
botId, _ := c.ParamsInt("botId", 0)
|
||||
if botId > 0 {
|
||||
var bot models.Account
|
||||
if err := database.C.Where("automated_id = ? AND id = ?", user.ID, botId).First(&bot).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("bot not found: %v", err))
|
||||
}
|
||||
target = bot
|
||||
}
|
||||
|
||||
key, err := services.NewApiKey(target, models.ApiKey{
|
||||
Name: data.Name,
|
||||
Description: data.Description,
|
||||
Lifecycle: data.Lifecycle,
|
||||
}, c.IP(), c.Get(fiber.HeaderUserAgent), data.Claims)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(key)
|
||||
}
|
||||
|
||||
func editBotKey(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var data struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
Lifecycle *int64 `json:"lifecycle"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, _ := c.ParamsInt("id", 0)
|
||||
|
||||
var tx *gorm.DB
|
||||
|
||||
botId, _ := c.ParamsInt("botId", 0)
|
||||
if botId > 0 {
|
||||
var bot models.Account
|
||||
if err := database.C.Where("automated_id = ? AND id = ?", user.ID, botId).First(&bot).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("bot not found: %v", err))
|
||||
}
|
||||
tx = database.C.Where("account_id = ?", bot.ID)
|
||||
} else {
|
||||
tx = database.C.Where("account_id = ?", user.ID)
|
||||
}
|
||||
|
||||
var key models.ApiKey
|
||||
if err := tx.Where("id = ?", id).First(&key).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
key.Name = data.Name
|
||||
key.Description = data.Description
|
||||
key.Lifecycle = data.Lifecycle
|
||||
|
||||
if err := database.C.Save(&key).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(key)
|
||||
}
|
||||
|
||||
func rollBotKey(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
id, _ := c.ParamsInt("id", 0)
|
||||
|
||||
var tx *gorm.DB
|
||||
|
||||
botId, _ := c.ParamsInt("botId", 0)
|
||||
if botId > 0 {
|
||||
var bot models.Account
|
||||
if err := database.C.Where("automated_id = ? AND id = ?", user.ID, botId).First(&bot).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("bot not found: %v", err))
|
||||
}
|
||||
tx = database.C.Where("account_id = ?", bot.ID)
|
||||
} else {
|
||||
tx = database.C.Where("account_id = ?", user.ID)
|
||||
}
|
||||
|
||||
var key models.ApiKey
|
||||
if err := tx.Where("id = ?", id).First(&key).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if key, err := services.RollApiKey(key); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.JSON(key)
|
||||
}
|
||||
}
|
||||
|
||||
func revokeBotKey(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
id, _ := c.ParamsInt("id", 0)
|
||||
|
||||
var tx *gorm.DB
|
||||
|
||||
botId, _ := c.ParamsInt("botId", 0)
|
||||
if botId > 0 {
|
||||
var bot models.Account
|
||||
if err := database.C.Where("automated_id = ? AND id = ?", user.ID, botId).First(&bot).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("bot not found: %v", err))
|
||||
}
|
||||
tx = database.C.Where("account_id = ?", bot.ID)
|
||||
} else {
|
||||
tx = database.C.Where("account_id = ?", user.ID)
|
||||
}
|
||||
|
||||
var key models.ApiKey
|
||||
if err := tx.Where("id = ?", id).First(&key).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := database.C.Delete(&key).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(key)
|
||||
}
|
101
pkg/internal/web/api/bots_api.go
Normal file
101
pkg/internal/web/api/bots_api.go
Normal file
@ -0,0 +1,101 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
"gorm.io/datatypes"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func listBots(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
tx := database.C.Where("automated_id = ?", user.ID)
|
||||
|
||||
countTx := tx
|
||||
var count int64
|
||||
if err := countTx.Model(&models.Account{}).Count(&count).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
var bots []models.Account
|
||||
if err := tx.Find(&bots).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"count": count,
|
||||
"data": bots,
|
||||
})
|
||||
}
|
||||
|
||||
func createBot(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
cnt, _ := services.GetBotCount(user)
|
||||
if err := exts.EnsureGrantedPerm(c, "CreateBots", cnt+1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var data struct {
|
||||
Name string `json:"name" validate:"required,lowercase,alphanum,min=4,max=16"`
|
||||
Nick string `json:"nick" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
} else {
|
||||
data.Name = strings.TrimSpace(data.Name)
|
||||
data.Nick = strings.TrimSpace(data.Nick)
|
||||
}
|
||||
|
||||
if !services.ValidateAccountName(data.Nick, 4, 24) {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "invalid bot nick, length requires 4 to 24")
|
||||
}
|
||||
|
||||
bot, err := services.NewBot(user, models.Account{
|
||||
Name: data.Name,
|
||||
Nick: data.Nick,
|
||||
Description: data.Description,
|
||||
ConfirmedAt: lo.ToPtr(time.Now()),
|
||||
PermNodes: datatypes.JSONMap{},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.JSON(bot)
|
||||
}
|
||||
}
|
||||
|
||||
func deleteBot(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
id, _ := c.ParamsInt("botId", 0)
|
||||
|
||||
var bot models.Account
|
||||
if err := database.C.Where("id = ? AND automated_id = ?", id, user.ID).First(&bot).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := services.DeleteAccount(bot.ID); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(bot)
|
||||
}
|
105
pkg/internal/web/api/check_in_api.go
Normal file
105
pkg/internal/web/api/check_in_api.go
Normal file
@ -0,0 +1,105 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func listCheckInRecord(c *fiber.Ctx) error {
|
||||
take := c.QueryInt("take", 0)
|
||||
offset := c.QueryInt("offset", 0)
|
||||
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var count int64
|
||||
if err := database.C.
|
||||
Model(&models.CheckInRecord{}).
|
||||
Where("account_id = ?", user.ID).
|
||||
Count(&count).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
var records []models.CheckInRecord
|
||||
if err := database.C.
|
||||
Where("account_id = ?", user.ID).
|
||||
Limit(take).Offset(offset).
|
||||
Order("created_at DESC").
|
||||
Find(&records).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"count": count,
|
||||
"data": records,
|
||||
})
|
||||
}
|
||||
|
||||
func listOtherUserCheckInRecord(c *fiber.Ctx) error {
|
||||
take := c.QueryInt("take", 0)
|
||||
offset := c.QueryInt("offset", 0)
|
||||
|
||||
alias := c.Params("alias")
|
||||
|
||||
var account models.Account
|
||||
if err := database.C.
|
||||
Where(&models.Account{Name: alias}).
|
||||
First(&account).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
var count int64
|
||||
if err := database.C.
|
||||
Model(&models.CheckInRecord{}).
|
||||
Where("account_id = ?", account.ID).
|
||||
Count(&count).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
var records []models.CheckInRecord
|
||||
if err := database.C.
|
||||
Where("account_id = ?", account.ID).
|
||||
Limit(take).Offset(offset).
|
||||
Order("created_at DESC").
|
||||
Find(&records).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"count": count,
|
||||
"data": records,
|
||||
})
|
||||
}
|
||||
|
||||
func getTodayCheckIn(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
if record, err := services.GetTodayCheckIn(user); err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else {
|
||||
return c.JSON(record)
|
||||
}
|
||||
}
|
||||
|
||||
func doCheckIn(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
if record, err := services.CheckIn(user); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "checkIn", strconv.Itoa(int(record.ID)), c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
return c.JSON(record)
|
||||
}
|
||||
}
|
127
pkg/internal/web/api/contacts_api.go
Normal file
127
pkg/internal/web/api/contacts_api.go
Normal file
@ -0,0 +1,127 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func listContact(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var contacts []models.AccountContact
|
||||
if err := database.C.Where("account_id = ?", user.ID).Find(&contacts).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(contacts)
|
||||
}
|
||||
|
||||
func getContact(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
contactId, _ := c.ParamsInt("contactId")
|
||||
|
||||
var contact models.AccountContact
|
||||
if err := database.C.Where("account_id = ? AND id = ?", user.ID, contactId).First(&contact).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(contact)
|
||||
}
|
||||
|
||||
func createContact(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var data struct {
|
||||
Type int8 `json:"type"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contact := models.AccountContact{
|
||||
Type: data.Type,
|
||||
Content: data.Content,
|
||||
IsPublic: data.IsPublic,
|
||||
IsPrimary: false,
|
||||
VerifiedAt: nil,
|
||||
AccountID: user.ID,
|
||||
}
|
||||
if err := database.C.Create(&contact).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(contact)
|
||||
}
|
||||
|
||||
func updateContact(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
contactId, _ := c.ParamsInt("contactId")
|
||||
|
||||
var data struct {
|
||||
Type int8 `json:"type"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var contact models.AccountContact
|
||||
if err := database.C.Where("account_id = ? AND id = ?", user.ID, contactId).First(&contact).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
contact.Type = data.Type
|
||||
contact.IsPublic = data.IsPublic
|
||||
if contact.Content != data.Content {
|
||||
contact.Content = data.Content
|
||||
contact.VerifiedAt = nil
|
||||
}
|
||||
|
||||
if err := database.C.Save(&contact).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(contact)
|
||||
}
|
||||
|
||||
func deleteContact(c *fiber.Ctx) error {
|
||||
if err := sec.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
contactId, _ := c.ParamsInt("contactId")
|
||||
|
||||
var contact models.AccountContact
|
||||
if err := database.C.Where("account_id = ? AND id = ?", user.ID, contactId).First(&contact).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := database.C.Delete(&contact).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
40
pkg/internal/web/api/events_api.go
Normal file
40
pkg/internal/web/api/events_api.go
Normal file
@ -0,0 +1,40 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func getEvents(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
take := c.QueryInt("take", 0)
|
||||
offset := c.QueryInt("offset", 0)
|
||||
|
||||
var count int64
|
||||
var events []models.ActionEvent
|
||||
if err := database.C.
|
||||
Where(&models.ActionEvent{AccountID: user.ID}).
|
||||
Model(&models.ActionEvent{}).
|
||||
Count(&count).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if err := database.C.
|
||||
Order("created_at desc").
|
||||
Where(&models.ActionEvent{AccountID: user.ID}).
|
||||
Limit(take).
|
||||
Offset(offset).
|
||||
Find(&events).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"count": count,
|
||||
"data": events,
|
||||
})
|
||||
}
|
167
pkg/internal/web/api/factors_api.go
Normal file
167
pkg/internal/web/api/factors_api.go
Normal file
@ -0,0 +1,167 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func getAvailableFactors(c *fiber.Ctx) error {
|
||||
ticketId := c.QueryInt("ticketId", 0)
|
||||
if ticketId <= 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "must provide ticket id as a query parameter")
|
||||
}
|
||||
|
||||
ticket, err := services.GetTicket(uint(ticketId))
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("ticket was not found: %v", err))
|
||||
}
|
||||
factors, err := services.ListUserFactor(ticket.AccountID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(factors)
|
||||
}
|
||||
|
||||
func requestFactorToken(c *fiber.Ctx) error {
|
||||
id, _ := c.ParamsInt("factorId", 0)
|
||||
|
||||
factor, err := services.GetFactor(uint(id))
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if sent, err := services.GetFactorCode(factor, c.IP()); err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else if !sent {
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
} else {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
func listFactor(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var factors []models.AuthFactor
|
||||
if err := database.C.Where("account_id = ?", user.ID).Find(&factors).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(factors)
|
||||
}
|
||||
|
||||
func createFactor(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var data struct {
|
||||
Type models.AuthFactorType `json:"type"`
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
typeWhitelist := []models.AuthFactorType{
|
||||
models.EmailPasswordFactor,
|
||||
models.InAppNotifyFactor,
|
||||
models.TimeOtpFactor,
|
||||
}
|
||||
if !lo.Contains(typeWhitelist, data.Type) {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "invalid factor type")
|
||||
}
|
||||
|
||||
// Currently, each type of factor can only be created once
|
||||
var currentCount int64
|
||||
if err := database.C.Model(&models.AuthFactor{}).
|
||||
Where("account_id = ? AND type = ?", user.ID, data.Type).
|
||||
Count(¤tCount).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("unable to check current factor count: %v", err))
|
||||
} else if currentCount > 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "this type of factor already exists")
|
||||
}
|
||||
|
||||
factor := models.AuthFactor{
|
||||
Type: data.Type,
|
||||
Secret: data.Secret,
|
||||
Account: user,
|
||||
AccountID: user.ID,
|
||||
}
|
||||
|
||||
additionalOnceConfig := map[string]any{}
|
||||
|
||||
switch data.Type {
|
||||
case models.TimeOtpFactor:
|
||||
cfg := totp.GenerateOpts{
|
||||
Issuer: viper.GetString("name"),
|
||||
AccountName: user.Name,
|
||||
Period: 30,
|
||||
SecretSize: 20,
|
||||
Digits: 6,
|
||||
}
|
||||
key, err := totp.Generate(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to generate totp key: %v", err)
|
||||
}
|
||||
factor.Secret = key.Secret()
|
||||
factor.Config = map[string]any{
|
||||
"issuer": cfg.Issuer,
|
||||
"account_name": cfg.AccountName,
|
||||
"period": cfg.Period,
|
||||
"secret_size": cfg.SecretSize,
|
||||
"digits": cfg.Digits,
|
||||
}
|
||||
additionalOnceConfig["url"] = key.URL()
|
||||
}
|
||||
|
||||
if err := database.C.Create(&factor).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if len(additionalOnceConfig) > 0 {
|
||||
for k, v := range additionalOnceConfig {
|
||||
factor.Config[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return c.JSON(factor)
|
||||
}
|
||||
|
||||
func deleteFactor(c *fiber.Ctx) error {
|
||||
id, _ := c.ParamsInt("factorId", 0)
|
||||
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var factor models.AuthFactor
|
||||
if err := database.C.Where("id = ? AND account_id = ?", id, user.ID).First(&factor).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if factor.Type == models.PasswordAuthFactor {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "unable to delete password factor")
|
||||
}
|
||||
|
||||
if err := database.C.Delete(&factor).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
194
pkg/internal/web/api/index.go
Normal file
194
pkg/internal/web/api/index.go
Normal file
@ -0,0 +1,194 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func MapControllers(app *fiber.App, baseURL string) {
|
||||
api := app.Group(baseURL).Name("API")
|
||||
{
|
||||
api.Get("/well-known/openid-configuration", getOidcConfiguration)
|
||||
api.Get("/well-known/jwks", getJwk)
|
||||
|
||||
checkIn := api.Group("/check-in").Name("Daily Check In API")
|
||||
{
|
||||
checkIn.Get("/", listCheckInRecord)
|
||||
checkIn.Get("/today", getTodayCheckIn)
|
||||
checkIn.Post("/", doCheckIn)
|
||||
}
|
||||
|
||||
notify := api.Group("/notifications").Name("Notifications API")
|
||||
{
|
||||
notify.Get("/", getNotifications)
|
||||
notify.Get("/count", getNotificationCount)
|
||||
notify.Get("/subscription", getNotifySubscriber)
|
||||
notify.Post("/subscription", addNotifySubscriber)
|
||||
notify.Delete("/subscription/:deviceId", removeNotifySubscriber)
|
||||
notify.Put("/read", markNotificationReadBatch)
|
||||
notify.Put("/read/all", markNotificationAllRead)
|
||||
notify.Put("/read/:notificationId", markNotificationRead)
|
||||
}
|
||||
|
||||
preferences := api.Group("/preferences").Name("Preferences API")
|
||||
{
|
||||
preferences.Get("/auth", getAuthPreference)
|
||||
preferences.Put("/auth", updateAuthPreference)
|
||||
preferences.Get("/notifications", getNotificationPreference)
|
||||
preferences.Put("/notifications", updateNotificationPreference)
|
||||
}
|
||||
|
||||
reports := api.Group("/reports").Name("Reports API")
|
||||
{
|
||||
abuse := reports.Group("/abuse").Name("Abuse Reports")
|
||||
{
|
||||
abuse.Get("/", listAbuseReports)
|
||||
abuse.Get("/:id", getAbuseReport)
|
||||
abuse.Put("/:id/status", updateAbuseReportStatus)
|
||||
abuse.Post("/", createAbuseReport)
|
||||
}
|
||||
}
|
||||
|
||||
api.Get("/users", getUserInBatch)
|
||||
api.Get("/users/lookup", lookupAccount)
|
||||
api.Get("/users/search", searchAccount)
|
||||
|
||||
me := api.Group("/users/me").Name("Myself Operations")
|
||||
{
|
||||
me.Get("/avatar", getAvatar)
|
||||
me.Get("/banner", getBanner)
|
||||
me.Put("/avatar", setAvatar)
|
||||
me.Put("/banner", setBanner)
|
||||
|
||||
me.Get("/", getUserinfo)
|
||||
me.Get("/oidc", getUserinfoForOidc)
|
||||
me.Put("/", updateUserinfo)
|
||||
me.Put("/language", updateAccountLanguage)
|
||||
me.Get("/events", getEvents)
|
||||
me.Get("/tickets", getTickets)
|
||||
me.Delete("/tickets/:ticketId", killTicket)
|
||||
|
||||
me.Post("/confirm", doRegisterConfirm)
|
||||
|
||||
me.Get("/status", getMyselfStatus)
|
||||
me.Post("/status", setStatus)
|
||||
me.Put("/status", editStatus)
|
||||
me.Delete("/status", clearStatus)
|
||||
|
||||
contacts := me.Group("/contacts").Name("Contacts")
|
||||
{
|
||||
contacts.Get("/", listContact)
|
||||
contacts.Get("/:contactId", getContact)
|
||||
contacts.Post("/", createContact)
|
||||
contacts.Put("/:contactId", updateContact)
|
||||
contacts.Delete("/:contactId", deleteContact)
|
||||
}
|
||||
|
||||
factors := me.Group("/factors").Name("Factors")
|
||||
{
|
||||
factors.Get("/", listFactor)
|
||||
factors.Post("/", createFactor)
|
||||
factors.Delete("/:factorId", deleteFactor)
|
||||
}
|
||||
|
||||
relations := me.Group("/relations").Name("Relations")
|
||||
{
|
||||
relations.Post("/", makeFriendship)
|
||||
relations.Post("/friend", makeFriendship)
|
||||
relations.Post("/block", makeBlockship)
|
||||
|
||||
relations.Get("/", listRelationship)
|
||||
relations.Get("/:relatedId", getRelationship)
|
||||
relations.Put("/:relatedId", editRelationship)
|
||||
relations.Delete("/:relatedId", deleteRelationship)
|
||||
|
||||
relations.Post("/:relatedId", makeFriendship)
|
||||
relations.Post("/:relatedId/accept", acceptFriend)
|
||||
relations.Post("/:relatedId/decline", declineFriend)
|
||||
}
|
||||
|
||||
me.Post("/password-reset", requestResetPassword)
|
||||
me.Patch("/password-reset", confirmResetPassword)
|
||||
|
||||
me.Post("/deletion", requestDeleteAccount)
|
||||
me.Patch("/deletion", confirmDeleteAccount)
|
||||
}
|
||||
|
||||
directory := api.Group("/users/:alias").Name("User Directory")
|
||||
{
|
||||
directory.Get("/", getOtherUserinfo)
|
||||
directory.Get("/status", getStatus)
|
||||
|
||||
directory.Get("/check-in", listOtherUserCheckInRecord)
|
||||
}
|
||||
|
||||
api.Get("/users", getOtherUserinfoBatch)
|
||||
api.Post("/users", doRegister)
|
||||
|
||||
auth := api.Group("/auth").Name("Auth")
|
||||
{
|
||||
auth.Post("/", doAuthenticate)
|
||||
auth.Patch("/", doAuthTicketCheck)
|
||||
auth.Post("/token", getToken)
|
||||
|
||||
auth.Get("/tickets/:ticketId", getTicket)
|
||||
|
||||
auth.Get("/factors", getAvailableFactors)
|
||||
auth.Post("/factors/:factorId", requestFactorToken)
|
||||
|
||||
auth.Get("/o/authorize", tryAuthorizeThirdClient)
|
||||
auth.Post("/o/authorize", authorizeThirdClient)
|
||||
}
|
||||
|
||||
realms := api.Group("/realms").Name("Realms API")
|
||||
{
|
||||
realms.Get("/", listCommunityRealm)
|
||||
realms.Get("/me", listOwnedRealm)
|
||||
realms.Get("/me/available", listAvailableRealm)
|
||||
realms.Get("/:realm", getRealm)
|
||||
realms.Get("/:realm/members", listRealmMembers)
|
||||
realms.Get("/:realm/members/me", getMyRealmMember)
|
||||
realms.Post("/", createRealm)
|
||||
realms.Put("/:realmId", editRealm)
|
||||
realms.Delete("/:realmId", deleteRealm)
|
||||
realms.Post("/:realm/members", addRealmMember)
|
||||
realms.Delete("/:realm/members/:memberId", removeRealmMember)
|
||||
realms.Delete("/:realm/me", leaveRealm)
|
||||
}
|
||||
|
||||
developers := api.Group("/dev").Name("Developers API")
|
||||
{
|
||||
developers.Post("/notify/:user", notifyUser)
|
||||
developers.Post("/notify/all", notifyAllUser)
|
||||
|
||||
bots := developers.Group("/bots").Name("Bots")
|
||||
{
|
||||
bots.Get("/", listBots)
|
||||
bots.Post("/", createBot)
|
||||
bots.Delete("/:botId", deleteBot)
|
||||
|
||||
keys := bots.Group("/:botId/keys").Name("Bots' Keys")
|
||||
{
|
||||
keys.Get("/", listBotKeys)
|
||||
keys.Post("/", createBotKey)
|
||||
keys.Post("/:id/roll", rollBotKey)
|
||||
keys.Put("/:id", editBotKey)
|
||||
keys.Delete("/:id", revokeBotKey)
|
||||
}
|
||||
}
|
||||
|
||||
keys := developers.Group("/keys").Name("Own Bots' Keys")
|
||||
{
|
||||
keys.Get("/", listBotKeys)
|
||||
keys.Get("/:id", getBotKey)
|
||||
keys.Post("/", createBotKey)
|
||||
keys.Post("/:id/roll", rollBotKey)
|
||||
keys.Put("/:id", editBotKey)
|
||||
keys.Delete("/:id", revokeBotKey)
|
||||
}
|
||||
}
|
||||
|
||||
api.All("/*", func(c *fiber.Ctx) error {
|
||||
return fiber.ErrNotFound
|
||||
})
|
||||
}
|
||||
}
|
210
pkg/internal/web/api/notifications_api.go
Normal file
210
pkg/internal/web/api/notifications_api.go
Normal file
@ -0,0 +1,210 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func getNotifications(c *fiber.Ctx) error {
|
||||
take := c.QueryInt("take", 0)
|
||||
offset := c.QueryInt("offset", 0)
|
||||
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
tx := database.C.Where(&models.Notification{AccountID: user.ID}).Model(&models.Notification{})
|
||||
|
||||
var count int64
|
||||
if err := tx.
|
||||
Count(&count).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
var notifications []models.Notification
|
||||
if err := tx.
|
||||
Limit(take).
|
||||
Offset(offset).
|
||||
Order("created_at DESC").
|
||||
Find(¬ifications).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"count": count,
|
||||
"data": notifications,
|
||||
})
|
||||
}
|
||||
|
||||
func getNotificationCount(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
tx := database.C.Where("account_id = ? AND read_at IS NULL", user.ID).Model(&models.Notification{})
|
||||
|
||||
var count int64
|
||||
if err := tx.
|
||||
Count(&count).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"count": count,
|
||||
})
|
||||
}
|
||||
|
||||
func markNotificationRead(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
id, _ := c.ParamsInt("notificationId", 0)
|
||||
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var notify models.Notification
|
||||
if err := database.C.Where(&models.Notification{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
AccountID: user.ID,
|
||||
}).First(¬ify).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
notify.ReadAt = lo.ToPtr(time.Now())
|
||||
|
||||
if err := database.C.Save(¬ify).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "notifications.mark.read", strconv.Itoa(int(notify.ID)), c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
func markNotificationReadBatch(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var data struct {
|
||||
MessageIDs []uint `json:"messages"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
if err := database.C.Model(&models.Notification{}).
|
||||
Where("account_id = ? AND id IN ?", user.ID, data.MessageIDs).
|
||||
Updates(&models.Notification{ReadAt: lo.ToPtr(time.Now())}).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "notifications.markBatch.read", strconv.Itoa(int(user.ID)), c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
func markNotificationAllRead(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
if tx := database.C.Model(&models.Notification{}).
|
||||
Where("account_id = ? AND read_at IS NULL", user.ID).
|
||||
Updates(&models.Notification{ReadAt: lo.ToPtr(time.Now())}); tx.Error != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, tx.Error.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "notifications.markAll.read", strconv.Itoa(int(user.ID)), c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
return c.JSON(fiber.Map{
|
||||
"count": tx.RowsAffected,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getNotifySubscriber(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var subscribers []models.NotificationSubscriber
|
||||
if err := database.C.Where(&models.NotificationSubscriber{
|
||||
AccountID: user.ID,
|
||||
}).Find(&subscribers).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(subscribers)
|
||||
}
|
||||
|
||||
func addNotifySubscriber(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var data struct {
|
||||
Provider string `json:"provider" validate:"required"`
|
||||
DeviceToken string `json:"device_token" validate:"required"`
|
||||
DeviceID string `json:"device_id" validate:"required"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var count int64
|
||||
if err := database.C.Where(&models.NotificationSubscriber{
|
||||
DeviceID: data.DeviceID,
|
||||
DeviceToken: data.DeviceToken,
|
||||
AccountID: user.ID,
|
||||
}).Model(&models.NotificationSubscriber{}).Count(&count).Error; err != nil || count > 0 {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
subscriber, err := services.AddNotifySubscriber(
|
||||
user,
|
||||
data.Provider,
|
||||
data.DeviceID,
|
||||
data.DeviceToken,
|
||||
c.Get(fiber.HeaderUserAgent),
|
||||
)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
services.AddEvent(user.ID, "notifications.subscribe.push", data.DeviceID, c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
return c.JSON(subscriber)
|
||||
}
|
||||
|
||||
func removeNotifySubscriber(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
device := c.Params("deviceId")
|
||||
|
||||
if err := database.C.Where(&models.NotificationSubscriber{
|
||||
DeviceID: device,
|
||||
AccountID: user.ID,
|
||||
}).Delete(&models.NotificationSubscriber{}).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
services.AddEvent(user.ID, "notifications.unsubscribe.push", device, c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
125
pkg/internal/web/api/notify_api.go
Normal file
125
pkg/internal/web/api/notify_api.go
Normal file
@ -0,0 +1,125 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func notifyUser(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureGrantedPerm(c, "DevNotifyUser", true); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var data struct {
|
||||
ClientID string `json:"client_id" validate:"required"`
|
||||
Topic string `json:"type" validate:"required"`
|
||||
Title string `json:"subject" validate:"required,max=1024"`
|
||||
Subtitle string `json:"subtitle" validate:"max=1024"`
|
||||
Body string `json:"content" validate:"required,max=4096"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
Priority int `json:"priority"`
|
||||
IsRealtime bool `json:"is_realtime"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := services.GetThirdClientWithUser(data.ClientID, user.ID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable to get client: %v", err))
|
||||
}
|
||||
|
||||
userId, _ := c.ParamsInt("user")
|
||||
|
||||
var target models.Account
|
||||
if target, err = services.GetAccount(uint(userId)); err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
notification := models.Notification{
|
||||
Topic: data.Topic,
|
||||
Subtitle: data.Subtitle,
|
||||
Title: data.Title,
|
||||
Body: data.Body,
|
||||
Metadata: data.Metadata,
|
||||
Priority: data.Priority,
|
||||
Account: target,
|
||||
AccountID: target.ID,
|
||||
SenderID: &client.ID,
|
||||
}
|
||||
|
||||
if data.IsRealtime {
|
||||
if err := services.PushNotification(notification); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
} else {
|
||||
if err := services.NewNotification(notification); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
func notifyAllUser(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureGrantedPerm(c, "DevNotifyAllUser", true); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var data struct {
|
||||
ClientID string `json:"client_id" validate:"required"`
|
||||
Topic string `json:"type" validate:"required"`
|
||||
Title string `json:"subject" validate:"required,max=1024"`
|
||||
Subtitle string `json:"subtitle" validate:"max=1024"`
|
||||
Body string `json:"content" validate:"required,max=4096"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
Priority int `json:"priority"`
|
||||
IsRealtime bool `json:"is_realtime"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := services.GetThirdClientWithUser(data.ClientID, user.ID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable to get client: %v", err))
|
||||
}
|
||||
|
||||
var accounts []models.Account
|
||||
if err := database.C.Find(&accounts).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
var notifications []models.Notification
|
||||
for _, account := range accounts {
|
||||
notification := models.Notification{
|
||||
Topic: data.Topic,
|
||||
Subtitle: data.Subtitle,
|
||||
Title: data.Title,
|
||||
Body: data.Body,
|
||||
Metadata: data.Metadata,
|
||||
Priority: data.Priority,
|
||||
Account: account,
|
||||
AccountID: account.ID,
|
||||
SenderID: &client.ID,
|
||||
}
|
||||
notifications = append(notifications, notification)
|
||||
}
|
||||
|
||||
if data.IsRealtime {
|
||||
go services.PushNotificationBatch(notifications)
|
||||
} else {
|
||||
go services.NewNotificationBatch(notifications)
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
132
pkg/internal/web/api/oauth_api.go
Executable file
132
pkg/internal/web/api/oauth_api.go
Executable file
@ -0,0 +1,132 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func tryAuthorizeThirdClient(c *fiber.Ctx) error {
|
||||
id := c.Query("client_id")
|
||||
redirect := c.Query("redirect_uri")
|
||||
|
||||
if len(id) <= 0 || len(redirect) <= 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "invalid request, missing query parameters")
|
||||
}
|
||||
|
||||
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 callback url")
|
||||
}
|
||||
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var ticket models.AuthTicket
|
||||
if err := database.C.Where(&models.AuthTicket{
|
||||
AccountID: user.ID,
|
||||
ClientID: &client.ID,
|
||||
}).Where("last_grant_at IS NULL").First(&ticket).Error; err == nil {
|
||||
if ticket.ExpiredAt != nil && ticket.ExpiredAt.Unix() < time.Now().Unix() {
|
||||
return c.JSON(fiber.Map{
|
||||
"client": client,
|
||||
"ticket": nil,
|
||||
})
|
||||
} else {
|
||||
ticket, _ = services.RotateTicket(ticket)
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"client": client,
|
||||
"ticket": ticket,
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"client": client,
|
||||
"ticket": nil,
|
||||
})
|
||||
}
|
||||
|
||||
func authorizeThirdClient(c *fiber.Ctx) error {
|
||||
id := c.Query("client_id")
|
||||
response := c.Query("response_type")
|
||||
redirect := c.Query("redirect_uri")
|
||||
nonce := c.Query("nonce")
|
||||
scope := c.Query("scope")
|
||||
if len(scope) <= 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "invalid request params")
|
||||
}
|
||||
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
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
|
||||
ticket, err := services.NewOauthTicket(
|
||||
user,
|
||||
client,
|
||||
strings.Split(scope, " "),
|
||||
[]string{services.InternalTokenAudience, client.Alias},
|
||||
c.IP(),
|
||||
c.Get(fiber.HeaderUserAgent),
|
||||
&nonce,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "oauth.connect", client.Alias, c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
return c.JSON(fiber.Map{
|
||||
"ticket": ticket,
|
||||
"redirect_uri": redirect,
|
||||
})
|
||||
}
|
||||
case "token":
|
||||
// OAuth Implicit Mode
|
||||
ticket, err := services.NewOauthTicket(
|
||||
user,
|
||||
client,
|
||||
strings.Split(scope, " "),
|
||||
[]string{services.InternalTokenAudience, client.Alias},
|
||||
c.IP(),
|
||||
c.Get(fiber.HeaderUserAgent),
|
||||
&nonce,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else if access, refresh, err := services.GetToken(ticket); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "oauth.connect", client.Alias, c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
return c.JSON(fiber.Map{
|
||||
"access_token": access,
|
||||
"refresh_token": refresh,
|
||||
"redirect_uri": redirect,
|
||||
"ticket": ticket,
|
||||
})
|
||||
}
|
||||
default:
|
||||
return fiber.NewError(fiber.StatusBadRequest, "unsupported response type")
|
||||
}
|
||||
}
|
47
pkg/internal/web/api/password_api.go
Normal file
47
pkg/internal/web/api/password_api.go
Normal file
@ -0,0 +1,47 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func requestResetPassword(c *fiber.Ctx) error {
|
||||
var data struct {
|
||||
UserID uint `json:"user_id" validate:"required"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user, err := services.GetAccount(data.UserID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
if err = services.CheckAbleToResetPassword(user); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else if err = services.RequestResetPassword(user); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
func confirmResetPassword(c *fiber.Ctx) error {
|
||||
var data struct {
|
||||
Code string `json:"code" validate:"required"`
|
||||
NewPassword string `json:"new_password" validate:"required"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := services.ConfirmResetPassword(data.Code, data.NewPassword); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
80
pkg/internal/web/api/preferences_api.go
Normal file
80
pkg/internal/web/api/preferences_api.go
Normal file
@ -0,0 +1,80 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func getAuthPreference(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
cfg, err := services.GetAuthPreference(user)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(cfg.Config.Data())
|
||||
}
|
||||
|
||||
func updateAuthPreference(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var data models.AuthConfig
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
cfg, err := services.UpdateAuthPreference(user, data)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "preferences.edit", "auth", c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
}
|
||||
|
||||
return c.JSON(cfg.Config.Data())
|
||||
}
|
||||
|
||||
func getNotificationPreference(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
notification, err := services.GetNotificationPreference(user)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(notification)
|
||||
}
|
||||
|
||||
func updateNotificationPreference(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var data struct {
|
||||
Config map[string]bool `json:"config"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
notification, err := services.UpdateNotificationPreference(user, data.Config)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "preferences.edit", "notifications", c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
}
|
||||
|
||||
return c.JSON(notification)
|
||||
}
|
144
pkg/internal/web/api/realm_members_api.go
Normal file
144
pkg/internal/web/api/realm_members_api.go
Normal file
@ -0,0 +1,144 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func listRealmMembers(c *fiber.Ctx) error {
|
||||
alias := c.Params("realm")
|
||||
take := c.QueryInt("take", 0)
|
||||
offset := c.QueryInt("offset", 0)
|
||||
|
||||
if realm, err := services.GetRealmWithAlias(alias); err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else if count, err := services.CountRealmMember(realm.ID); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else if members, err := services.ListRealmMember(realm.ID, take, offset); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else {
|
||||
return c.JSON(fiber.Map{
|
||||
"count": count,
|
||||
"data": members,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getMyRealmMember(c *fiber.Ctx) error {
|
||||
alias := c.Params("realm")
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
if realm, err := services.GetRealmWithAlias(alias); err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else if member, err := services.GetRealmMember(user.ID, realm.ID); err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else {
|
||||
return c.JSON(member)
|
||||
}
|
||||
}
|
||||
|
||||
func addRealmMember(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
alias := c.Params("realm")
|
||||
|
||||
var data struct {
|
||||
Related string `json:"related" validate:"required"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
realm, err := services.GetRealmWithAlias(alias)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
var account models.Account
|
||||
var numericId int
|
||||
if numericId, err = strconv.Atoi(data.Related); err == nil {
|
||||
err = database.C.Where(&models.Account{
|
||||
BaseModel: models.BaseModel{ID: uint(numericId)},
|
||||
}).First(&account).Error
|
||||
} else {
|
||||
err = database.C.Where(&models.Account{
|
||||
Name: data.Related,
|
||||
}).First(&account).Error
|
||||
}
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := services.AddRealmMember(user, account, realm); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
func removeRealmMember(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
alias := c.Params("realm")
|
||||
memberId, _ := c.ParamsInt("memberId", 0)
|
||||
|
||||
realm, err := services.GetRealmWithAlias(alias)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
var member models.RealmMember
|
||||
if err := database.C.Where(&models.RealmMember{
|
||||
BaseModel: models.BaseModel{ID: uint(memberId)},
|
||||
RealmID: realm.ID,
|
||||
}).First(&member).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := services.RemoveRealmMember(user, member, realm); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
func leaveRealm(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
alias := c.Params("realm")
|
||||
|
||||
realm, err := services.GetRealmWithAlias(alias)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else if user.ID == realm.AccountID {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "you cannot leave your own realm")
|
||||
}
|
||||
|
||||
var member models.RealmMember
|
||||
if err := database.C.Where(&models.RealmMember{
|
||||
RealmID: realm.ID,
|
||||
AccountID: user.ID,
|
||||
}).First(&member).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := database.C.Delete(&member).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
}
|
168
pkg/internal/web/api/realms_api.go
Normal file
168
pkg/internal/web/api/realms_api.go
Normal file
@ -0,0 +1,168 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func getRealm(c *fiber.Ctx) error {
|
||||
alias := c.Params("realm")
|
||||
if realm, err := services.GetRealmWithAlias(alias); err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else {
|
||||
return c.JSON(realm)
|
||||
}
|
||||
}
|
||||
|
||||
func listCommunityRealm(c *fiber.Ctx) error {
|
||||
realms, err := services.ListCommunityRealm()
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(realms)
|
||||
}
|
||||
|
||||
func listOwnedRealm(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
if realms, err := services.ListOwnedRealm(user); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.JSON(realms)
|
||||
}
|
||||
}
|
||||
|
||||
func listAvailableRealm(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
if realms, err := services.ListAvailableRealm(user); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.JSON(realms)
|
||||
}
|
||||
}
|
||||
|
||||
func createRealm(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureGrantedPerm(c, "CreateRealms", true); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var data struct {
|
||||
Alias string `json:"alias" validate:"required,lowercase,min=4,max=32"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
Avatar *string `json:"avatar"`
|
||||
Banner *string `json:"banner"`
|
||||
AccessPolicy map[string]any `json:"access_policy"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
IsCommunity bool `json:"is_community"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
realm, err := services.NewRealm(models.Realm{
|
||||
Alias: data.Alias,
|
||||
Name: data.Name,
|
||||
Description: data.Description,
|
||||
Avatar: data.Avatar,
|
||||
Banner: data.Banner,
|
||||
AccessPolicy: data.AccessPolicy,
|
||||
IsPublic: data.IsPublic,
|
||||
IsCommunity: data.IsCommunity,
|
||||
AccountID: user.ID,
|
||||
}, user)
|
||||
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "realms.new", strconv.Itoa(int(realm.ID)), c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
}
|
||||
|
||||
return c.JSON(realm)
|
||||
}
|
||||
|
||||
func editRealm(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
id, _ := c.ParamsInt("realmId", 0)
|
||||
|
||||
var data struct {
|
||||
Alias string `json:"alias" validate:"required,lowercase,min=4,max=32"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
Avatar *string `json:"avatar"`
|
||||
Banner *string `json:"banner"`
|
||||
AccessPolicy map[string]any `json:"access_policy"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
IsCommunity bool `json:"is_community"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var realm models.Realm
|
||||
if err := database.C.Where(&models.Realm{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
AccountID: user.ID,
|
||||
}).First(&realm).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
realm.Alias = data.Alias
|
||||
realm.Name = data.Name
|
||||
realm.Description = data.Description
|
||||
realm.Avatar = data.Avatar
|
||||
realm.Banner = data.Banner
|
||||
realm.AccessPolicy = data.AccessPolicy
|
||||
realm.IsPublic = data.IsPublic
|
||||
realm.IsCommunity = data.IsCommunity
|
||||
|
||||
realm, err := services.EditRealm(realm)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "realms.edit", strconv.Itoa(int(realm.ID)), c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
}
|
||||
|
||||
return c.JSON(realm)
|
||||
}
|
||||
|
||||
func deleteRealm(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
id, _ := c.ParamsInt("realmId", 0)
|
||||
|
||||
var realm models.Realm
|
||||
if err := database.C.Where(&models.Realm{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
AccountID: user.ID,
|
||||
}).First(&realm).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := services.DeleteRealm(realm); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "realms.delete", strconv.Itoa(int(realm.ID)), c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
231
pkg/internal/web/api/relationships_api.go
Normal file
231
pkg/internal/web/api/relationships_api.go
Normal file
@ -0,0 +1,231 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func listRelationship(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
statusQuery := c.Query("status")
|
||||
|
||||
status := lo.Map(strings.Split(statusQuery, ","), func(s string, _ int) models.RelationshipStatus {
|
||||
num, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return models.RelationshipStatus(num)
|
||||
})
|
||||
|
||||
var err error
|
||||
var friends []models.AccountRelationship
|
||||
if len(status) == 0 {
|
||||
if friends, err = services.ListAllRelationship(user); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
} else {
|
||||
if friends, err = services.ListRelationshipWithFilter(user, status...); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return c.JSON(friends)
|
||||
}
|
||||
|
||||
func getRelationship(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
relatedId, _ := c.ParamsInt("relatedId", 0)
|
||||
|
||||
related, err := services.GetAccount(uint(relatedId))
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if friend, err := services.GetRelationWithTwoNode(user.ID, related.ID); err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else {
|
||||
return c.JSON(friend)
|
||||
}
|
||||
}
|
||||
|
||||
func editRelationship(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
relatedId, _ := c.ParamsInt("relatedId", 0)
|
||||
|
||||
var data struct {
|
||||
Status uint8 `json:"status"`
|
||||
PermNodes map[string]any `json:"perm_nodes"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
relationship, err := services.GetRelationWithTwoNode(user.ID, uint(relatedId))
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
relationship.Status = models.RelationshipStatus(data.Status)
|
||||
relationship.PermNodes = data.PermNodes
|
||||
|
||||
if friendship, err := services.EditRelationship(relationship); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "relationships.edit", strconv.Itoa(int(relationship.ID)), c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
return c.JSON(friendship)
|
||||
}
|
||||
}
|
||||
|
||||
func deleteRelationship(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
relatedId, _ := c.ParamsInt("relatedId", 0)
|
||||
|
||||
related, err := services.GetAccount(uint(relatedId))
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
relationship, err := services.GetRelationWithTwoNode(user.ID, related.ID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := services.DeleteRelationship(relationship); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "relationships.delete", strconv.Itoa(int(relationship.ID)), c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
return c.JSON(relationship)
|
||||
}
|
||||
}
|
||||
|
||||
// Friends stuff
|
||||
|
||||
func makeFriendship(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var data struct {
|
||||
Related string `json:"related" validate:"required"`
|
||||
}
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
var related models.Account
|
||||
if numericId, err := strconv.Atoi(data.Related); err == nil {
|
||||
related, err = services.GetAccount(uint(numericId))
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
} else {
|
||||
related, err = services.LookupAccount(data.Related)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
friend, err := services.NewFriend(user, related)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "relationships.friends.new", strconv.Itoa(int(related.ID)), c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
return c.JSON(friend)
|
||||
}
|
||||
}
|
||||
|
||||
func makeBlockship(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var data struct {
|
||||
Related string `json:"related" validate:"required"`
|
||||
}
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
var related models.Account
|
||||
if numericId, err := strconv.Atoi(data.Related); err == nil {
|
||||
related, err = services.GetAccount(uint(numericId))
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
} else {
|
||||
related, err = services.LookupAccount(data.Related)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
friend, err := services.NewBlockship(user, related)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "relationships.blocks.new", strconv.Itoa(int(related.ID)), c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
return c.JSON(friend)
|
||||
}
|
||||
}
|
||||
|
||||
func acceptFriend(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
relatedId, _ := c.ParamsInt("relatedId", 0)
|
||||
|
||||
related, err := services.GetAccount(uint(relatedId))
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := services.HandleFriend(user, related, true); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "relationships.friends.accept", strconv.Itoa(relatedId), c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
func declineFriend(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
relatedId, _ := c.ParamsInt("relatedId", 0)
|
||||
|
||||
related, err := services.GetAccount(uint(relatedId))
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := services.HandleFriend(user, related, false); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "relationships.friends.decline", strconv.Itoa(relatedId), c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
}
|
78
pkg/internal/web/api/reports_api.go
Normal file
78
pkg/internal/web/api/reports_api.go
Normal file
@ -0,0 +1,78 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func listAbuseReports(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
reports, err := services.ListAbuseReport(user)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(reports)
|
||||
}
|
||||
|
||||
func getAbuseReport(c *fiber.Ctx) error {
|
||||
id, _ := c.ParamsInt("id")
|
||||
report, err := services.GetAbuseReport(uint(id))
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(report)
|
||||
}
|
||||
|
||||
func updateAbuseReportStatus(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureGrantedPerm(c, "DealAbuseReport", true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var data struct {
|
||||
Status string `json:"status" validate:"required"`
|
||||
Message string `json:"message" validate:"required,max=4096"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, _ := c.ParamsInt("id")
|
||||
|
||||
if err := services.UpdateAbuseReportStatus(uint(id), data.Status, data.Message); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
func createAbuseReport(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var data struct {
|
||||
Resource string `json:"resource" validate:"required"`
|
||||
Reason string `json:"reason" validate:"required,max=4096"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
report, err := services.NewAbuseReport(data.Resource, data.Reason, user)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(report)
|
||||
}
|
57
pkg/internal/web/api/security_api.go
Normal file
57
pkg/internal/web/api/security_api.go
Normal file
@ -0,0 +1,57 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func getTickets(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
take := c.QueryInt("take", 0)
|
||||
offset := c.QueryInt("offset", 0)
|
||||
|
||||
var count int64
|
||||
var tickets []models.AuthTicket
|
||||
if err := database.C.
|
||||
Where(&models.AuthTicket{AccountID: user.ID}).
|
||||
Model(&models.AuthTicket{}).
|
||||
Count(&count).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if err := database.C.
|
||||
Order("created_at desc").
|
||||
Where(&models.AuthTicket{AccountID: user.ID}).
|
||||
Limit(take).
|
||||
Offset(offset).
|
||||
Find(&tickets).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"count": count,
|
||||
"data": tickets,
|
||||
})
|
||||
}
|
||||
|
||||
func killTicket(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
id, _ := c.ParamsInt("ticketId", 0)
|
||||
|
||||
if err := database.C.Delete(&models.AuthTicket{}, &models.AuthTicket{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
AccountID: user.ID,
|
||||
}).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
151
pkg/internal/web/api/statuses_api.go
Normal file
151
pkg/internal/web/api/statuses_api.go
Normal file
@ -0,0 +1,151 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func getStatus(c *fiber.Ctx) error {
|
||||
alias := c.Params("alias")
|
||||
|
||||
var user models.Account
|
||||
if err := database.C.Where(models.Account{
|
||||
Name: alias,
|
||||
}).Preload("Profile").First(&user).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("account not found: %s", err))
|
||||
}
|
||||
|
||||
status, err := services.GetStatus(user.ID)
|
||||
disturbable := services.GetStatusDisturbable(user.ID) == nil
|
||||
online := services.GetStatusOnline(user.ID) == nil
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"status": lo.Ternary(err == nil, &status, nil),
|
||||
"last_seen_at": user.Profile.LastSeenAt,
|
||||
"is_disturbable": disturbable,
|
||||
"is_online": online,
|
||||
})
|
||||
}
|
||||
|
||||
func getMyselfStatus(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
status, err := services.GetStatus(user.ID)
|
||||
disturbable := services.GetStatusDisturbable(user.ID) == nil
|
||||
online := services.GetStatusOnline(user.ID) == nil
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"status": lo.Ternary(err == nil, &status, nil),
|
||||
"last_seen_at": user.Profile.LastSeenAt,
|
||||
"is_disturbable": disturbable,
|
||||
"is_online": online,
|
||||
})
|
||||
}
|
||||
|
||||
func setStatus(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var req struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
Label string `json:"label" validate:"required"`
|
||||
Attitude uint `json:"attitude"`
|
||||
IsNoDisturb bool `json:"is_no_disturb"`
|
||||
IsInvisible bool `json:"is_invisible"`
|
||||
ClearAt *time.Time `json:"clear_at"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// End the status already exists
|
||||
if status, err := services.GetStatus(user.ID); err == nil {
|
||||
status.ClearAt = lo.ToPtr(time.Now())
|
||||
database.C.Save(&status)
|
||||
}
|
||||
|
||||
status := models.Status{
|
||||
Type: req.Type,
|
||||
Label: req.Label,
|
||||
Attitude: models.StatusAttitude(req.Attitude),
|
||||
IsNoDisturb: req.IsNoDisturb,
|
||||
IsInvisible: req.IsInvisible,
|
||||
ClearAt: req.ClearAt,
|
||||
AccountID: user.ID,
|
||||
}
|
||||
|
||||
if status, err := services.NewStatus(user, status); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "statuses.set", strconv.Itoa(int(status.ID)), c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
return c.JSON(status)
|
||||
}
|
||||
}
|
||||
|
||||
func editStatus(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var req struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
Label string `json:"label" validate:"required"`
|
||||
Attitude uint `json:"attitude"`
|
||||
IsNoDisturb bool `json:"is_no_disturb"`
|
||||
IsInvisible bool `json:"is_invisible"`
|
||||
ClearAt *time.Time `json:"clear_at"`
|
||||
}
|
||||
|
||||
if err := exts.BindAndValidate(c, &req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
status, err := services.GetStatus(user.ID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "you must set a status first and then can edit it")
|
||||
}
|
||||
|
||||
status.Type = req.Type
|
||||
status.Label = req.Label
|
||||
status.Attitude = models.StatusAttitude(req.Attitude)
|
||||
status.IsNoDisturb = req.IsNoDisturb
|
||||
status.IsInvisible = req.IsInvisible
|
||||
status.ClearAt = req.ClearAt
|
||||
|
||||
if status, err := services.EditStatus(user, status); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "statuses.edit", strconv.Itoa(int(status.ID)), c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
return c.JSON(status)
|
||||
}
|
||||
}
|
||||
|
||||
func clearStatus(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
if err := services.ClearStatus(user); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else {
|
||||
services.AddEvent(user.ID, "statuses.clear", strconv.Itoa(int(user.ID)), c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
129
pkg/internal/web/api/userinfo_api.go
Normal file
129
pkg/internal/web/api/userinfo_api.go
Normal file
@ -0,0 +1,129 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
|
||||
localCache "git.solsynth.dev/hypernet/passport/pkg/internal/cache"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
|
||||
"github.com/eko/gocache/lib/v4/cache"
|
||||
"github.com/eko/gocache/lib/v4/marshaler"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func getOtherUserinfo(c *fiber.Ctx) error {
|
||||
alias := c.Params("alias")
|
||||
|
||||
cacheManager := cache.New[any](localCache.S)
|
||||
marshal := marshaler.New(cacheManager)
|
||||
ctx := context.Background()
|
||||
|
||||
if val, err := marshal.Get(ctx, services.GetAccountCacheKey(alias), new(models.Account)); err == nil {
|
||||
return c.JSON(*val.(*models.Account))
|
||||
}
|
||||
|
||||
tx := database.C.Where("name = ?", alias)
|
||||
|
||||
numericId, err := strconv.Atoi(alias)
|
||||
if err == nil {
|
||||
if val, err := marshal.Get(ctx, services.GetAccountCacheKey(numericId), new(models.Account)); err == nil {
|
||||
return c.JSON(*val.(*models.Account))
|
||||
}
|
||||
tx = tx.Or("id = ?", numericId)
|
||||
}
|
||||
|
||||
var account models.Account
|
||||
if err := tx.
|
||||
Preload("Profile").
|
||||
Preload("Badges", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Order("badges.type DESC")
|
||||
}).
|
||||
First(&account).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
groups, err := services.GetUserAccountGroup(account)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("unable to get account groups: %v", err))
|
||||
}
|
||||
for _, group := range groups {
|
||||
for k, v := range group.PermNodes {
|
||||
if _, ok := account.PermNodes[k]; !ok {
|
||||
account.PermNodes[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
services.CacheAccount(account)
|
||||
|
||||
return c.JSON(account)
|
||||
}
|
||||
|
||||
func getOtherUserinfoBatch(c *fiber.Ctx) error {
|
||||
idFilter := c.Query("id")
|
||||
nameFilter := c.Query("name")
|
||||
idSet := strings.Split(idFilter, ",")
|
||||
nameSet := strings.Split(nameFilter, ",")
|
||||
if len(idSet) == 0 && len(nameSet) == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "query filter is required")
|
||||
}
|
||||
|
||||
if len(idSet)+len(nameSet) > 100 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "only support 100 users in a single batch")
|
||||
}
|
||||
|
||||
tx := database.C.Model(&models.Account{}).Limit(100)
|
||||
if len(idFilter) > 0 {
|
||||
tx = tx.Where("id IN ?", idSet)
|
||||
}
|
||||
if len(nameFilter) > 0 {
|
||||
tx = tx.Where("name IN ?", nameSet)
|
||||
}
|
||||
|
||||
var accounts []models.Account
|
||||
if err := tx.Find(&accounts).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(accounts)
|
||||
}
|
||||
|
||||
func getUserinfoForOidc(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
var data models.Account
|
||||
if err := database.C.
|
||||
Where(&models.Account{BaseModel: models.BaseModel{ID: user.ID}}).
|
||||
Preload("Profile").
|
||||
Preload("Contacts").
|
||||
Preload("Badges").
|
||||
First(&data).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
} else {
|
||||
data.PermNodes = c.Locals("nex_user").(*sec.UserInfo).PermNodes
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"sub": fmt.Sprintf("%d", data.ID),
|
||||
"family_name": data.Profile.FirstName,
|
||||
"given_name": data.Profile.LastName,
|
||||
"name": data.Name,
|
||||
"email": data.GetPrimaryEmail().Content,
|
||||
"email_verified": data.GetPrimaryEmail().VerifiedAt != nil,
|
||||
"preferred_username": data.Nick,
|
||||
"picture": data.GetAvatar(),
|
||||
"birthdate": data.Profile.Birthday,
|
||||
"updated_at": data.UpdatedAt,
|
||||
})
|
||||
}
|
36
pkg/internal/web/api/well_known_api.go
Normal file
36
pkg/internal/web/api/well_known_api.go
Normal file
@ -0,0 +1,36 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func getOidcConfiguration(c *fiber.Ctx) error {
|
||||
domain := viper.GetString("domain")
|
||||
basepath := fmt.Sprintf("https://%s", domain)
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"issuer": viper.GetString("security.issuer"),
|
||||
"authorization_endpoint": fmt.Sprintf("%s/authorize", basepath),
|
||||
"token_endpoint": fmt.Sprintf("%s/api/auth/token", basepath),
|
||||
"userinfo_endpoint": fmt.Sprintf("%s/api/users/me/oidc", basepath),
|
||||
"response_types_supported": []string{"code", "token"},
|
||||
"grant_types_supported": []string{"authorization_code", "implicit", "refresh_token"},
|
||||
"subject_types_supported": []string{"public"},
|
||||
"token_endpoint_auth_methods_supported": []string{"client_secret_post"},
|
||||
"id_token_signing_alg_values_supported": []string{"RS256"},
|
||||
"token_endpoint_auth_signing_alg_values_supported": []string{"RS256"},
|
||||
"jwks_uri": fmt.Sprintf("%s/.well-known/jwks", basepath),
|
||||
})
|
||||
}
|
||||
|
||||
func getJwk(c *fiber.Ctx) error {
|
||||
return c.JSON(fiber.Map{
|
||||
"keys": []fiber.Map{
|
||||
services.EReader.BuildJwk(viper.GetString("id")),
|
||||
},
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user