🎉 Init project from scratch
This commit is contained in:
35
pkg/server/auth.go
Normal file
35
pkg/server/auth.go
Normal file
@ -0,0 +1,35 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"code.smartsheep.studio/hydrogen/interactive/pkg/database"
|
||||
"code.smartsheep.studio/hydrogen/interactive/pkg/models"
|
||||
"code.smartsheep.studio/hydrogen/passport/pkg/security"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/keyauth"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var auth = keyauth.New(keyauth.Config{
|
||||
KeyLookup: "header:Authorization",
|
||||
AuthScheme: "Bearer",
|
||||
Validator: func(c *fiber.Ctx, token string) (bool, error) {
|
||||
claims, err := security.DecodeJwt(token)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
id, _ := strconv.Atoi(claims.Subject)
|
||||
|
||||
var user models.Account
|
||||
if err := database.C.Where(&models.Account{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
}).First(&user).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
c.Locals("principal", user)
|
||||
|
||||
return true, nil
|
||||
},
|
||||
ContextKey: "token",
|
||||
})
|
87
pkg/server/auth_api.go
Normal file
87
pkg/server/auth_api.go
Normal file
@ -0,0 +1,87 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"code.smartsheep.studio/hydrogen/interactive/pkg/services"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
"github.com/spf13/viper"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var cfg = oauth2.Config{
|
||||
RedirectURL: fmt.Sprintf("https://%s/api/auth/callback", viper.GetString("domain")),
|
||||
ClientID: viper.GetString("passport.client_id"),
|
||||
ClientSecret: viper.GetString("passport.client_secret"),
|
||||
Scopes: []string{"openid"},
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: fmt.Sprintf("%s/auth/o/connect", viper.GetString("passport.endpoint")),
|
||||
TokenURL: fmt.Sprintf("%s/api/auth/token", viper.GetString("passport.endpoint")),
|
||||
AuthStyle: oauth2.AuthStyleInParams,
|
||||
},
|
||||
}
|
||||
|
||||
func doLogin(c *fiber.Ctx) error {
|
||||
url := cfg.AuthCodeURL(uuid.NewString())
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"target": url,
|
||||
})
|
||||
}
|
||||
|
||||
func doPostLogin(c *fiber.Ctx) error {
|
||||
code := c.Query("code")
|
||||
|
||||
token, err := cfg.Exchange(context.Background(), code)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("failed to exchange token: %q", err))
|
||||
}
|
||||
|
||||
agent := fiber.
|
||||
Get(fmt.Sprintf("%s/api/users/me", viper.GetString("passport.endpoint"))).
|
||||
Set(fiber.HeaderAuthorization, fmt.Sprintf("Bearer %s", token.AccessToken))
|
||||
|
||||
_, body, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("failed to get userinfo: %q", errs))
|
||||
}
|
||||
|
||||
var userinfo services.PassportUserinfo
|
||||
err = json.Unmarshal(body, &userinfo)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("failed to parse userinfo: %q", err))
|
||||
}
|
||||
|
||||
account, err := services.LinkAccount(userinfo)
|
||||
access, refresh, err := services.GetToken(account)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("failed to get token: %q", err))
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"access_token": access,
|
||||
"refresh_token": refresh,
|
||||
})
|
||||
}
|
||||
|
||||
func doRefreshToken(c *fiber.Ctx) error {
|
||||
var data struct {
|
||||
RefreshToken string `json:"refresh_token" validate:"required"`
|
||||
}
|
||||
|
||||
if err := BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
access, refresh, err := services.RefreshToken(data.RefreshToken)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("failed to get token: %q", err))
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"access_token": access,
|
||||
"refresh_token": refresh,
|
||||
})
|
||||
}
|
79
pkg/server/startup.go
Normal file
79
pkg/server/startup.go
Normal file
@ -0,0 +1,79 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"code.smartsheep.studio/hydrogen/interactive/pkg/view"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cache"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/gofiber/fiber/v2/middleware/filesystem"
|
||||
"github.com/gofiber/fiber/v2/middleware/idempotency"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var A *fiber.App
|
||||
|
||||
func NewServer() {
|
||||
A = fiber.New(fiber.Config{
|
||||
DisableStartupMessage: true,
|
||||
EnableIPValidation: true,
|
||||
ServerHeader: "Hydrogen.Interactive",
|
||||
AppName: "Hydrogen.Interactive",
|
||||
ProxyHeader: fiber.HeaderXForwardedFor,
|
||||
JSONEncoder: jsoniter.ConfigCompatibleWithStandardLibrary.Marshal,
|
||||
JSONDecoder: jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal,
|
||||
EnablePrintRoutes: viper.GetBool("debug"),
|
||||
})
|
||||
|
||||
A.Use(idempotency.New())
|
||||
A.Use(cors.New(cors.Config{
|
||||
AllowCredentials: true,
|
||||
AllowMethods: strings.Join([]string{
|
||||
fiber.MethodGet,
|
||||
fiber.MethodPost,
|
||||
fiber.MethodHead,
|
||||
fiber.MethodOptions,
|
||||
fiber.MethodPut,
|
||||
fiber.MethodDelete,
|
||||
fiber.MethodPatch,
|
||||
}, ","),
|
||||
AllowOriginsFunc: func(origin string) bool {
|
||||
return true
|
||||
},
|
||||
}))
|
||||
|
||||
A.Use(logger.New(logger.Config{
|
||||
Format: "${status} | ${latency} | ${method} ${path}\n",
|
||||
Output: log.Logger,
|
||||
}))
|
||||
|
||||
A.Get("/.well-known", getMetadata)
|
||||
|
||||
api := A.Group("/api").Name("API")
|
||||
{
|
||||
api.Get("/auth", doLogin)
|
||||
api.Get("/auth/callback", doPostLogin)
|
||||
api.Post("/auth/refresh", doRefreshToken)
|
||||
}
|
||||
|
||||
A.Use("/", cache.New(cache.Config{
|
||||
Expiration: 24 * time.Hour,
|
||||
CacheControl: true,
|
||||
}), filesystem.New(filesystem.Config{
|
||||
Root: http.FS(view.FS),
|
||||
PathPrefix: "dist",
|
||||
Index: "index.html",
|
||||
NotFoundFile: "dist/index.html",
|
||||
}))
|
||||
}
|
||||
|
||||
func Listen() {
|
||||
if err := A.Listen(viper.GetString("bind")); err != nil {
|
||||
log.Fatal().Err(err).Msg("An error occurred when starting server...")
|
||||
}
|
||||
}
|
18
pkg/server/utils.go
Normal file
18
pkg/server/utils.go
Normal file
@ -0,0 +1,18 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
var validation = validator.New(validator.WithRequiredStructEnabled())
|
||||
|
||||
func BindAndValidate(c *fiber.Ctx, out any) error {
|
||||
if err := c.BodyParser(out); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else if err := validation.Struct(out); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
13
pkg/server/well_known_api.go
Normal file
13
pkg/server/well_known_api.go
Normal file
@ -0,0 +1,13 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func getMetadata(c *fiber.Ctx) error {
|
||||
return c.JSON(fiber.Map{
|
||||
"name": viper.GetString("name"),
|
||||
"domain": viper.GetString("domain"),
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user