@@ -1,32 +1 @@
|
|||||||
|
TZ=Asia/Shanghai
|
||||||
SERVER_APPLICATION=nixcn-cms
|
|
||||||
SERVER_ADDRESS=:8000
|
|
||||||
SERVER_EXTERNAL_URL=http://test.sne.moe:8080
|
|
||||||
SERVER_DEBUG_MODE=true
|
|
||||||
SERVER_FILE_LOGGER=false
|
|
||||||
|
|
||||||
DATABASE_TYPE=postgres
|
|
||||||
DATABASE_HOST=localhost:5432
|
|
||||||
DATABASE_NAME=postgres
|
|
||||||
DATABASE_USERNAME=postgres
|
|
||||||
DATABASE_PASSWORD=postgres
|
|
||||||
|
|
||||||
CACHE_HOSTS=localhost:6379
|
|
||||||
CACHE_MASTER=
|
|
||||||
CACHE_USERNAME=
|
|
||||||
CACHE_PASSWORD=
|
|
||||||
CACHE_DB=0
|
|
||||||
|
|
||||||
SEARCH_HOST=localhost
|
|
||||||
SEARCH_API_KEY=
|
|
||||||
|
|
||||||
EMAIL_RESEND_API_KEY=re_BMJaPVVB_kgdf1Go7n3dWVywp6hp4WmSA
|
|
||||||
EMAIL_FROM=NixCN CMS Email Verify <nixcn@violet.sne.moe>
|
|
||||||
|
|
||||||
SECRETS_JWT_SECRET=6Wd5xkDkF4XX5q2Ckq6TY6WX
|
|
||||||
SECRETS_TURNSTILE_SECRET=0x4AAAAAACI5pgVONOZ0rzyAYsdUcoOBF8w
|
|
||||||
|
|
||||||
TTL_MAGIC_LINK_TTL=10m
|
|
||||||
TTL_ACCESS_TTL=15s
|
|
||||||
TTL_REFRESH_TTL=168h
|
|
||||||
TTL_CHECKIN_COSE_TTL=10m
|
|
||||||
|
|||||||
@@ -28,5 +28,5 @@ secrets:
|
|||||||
ttl:
|
ttl:
|
||||||
magic_link_ttl: 10m
|
magic_link_ttl: 10m
|
||||||
jwt_ttl: 15s
|
jwt_ttl: 15s
|
||||||
refresh_ttl: 7d
|
refresh_ttl: 168h
|
||||||
checkin_code_ttl: 5m
|
checkin_code_ttl: 10m
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ type config struct {
|
|||||||
type server struct {
|
type server struct {
|
||||||
Application string `yaml:"application"`
|
Application string `yaml:"application"`
|
||||||
Address string `yaml:"address"`
|
Address string `yaml:"address"`
|
||||||
|
ExternalUrl string `yaml:"external_url"`
|
||||||
DebugMode string `yaml:"debug_mode"`
|
DebugMode string `yaml:"debug_mode"`
|
||||||
FileLogger string `yaml:"file_logger"`
|
FileLogger string `yaml:"file_logger"`
|
||||||
JwtSecret string `yaml:"jwt_secret"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type database struct {
|
type database struct {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
packages = [
|
packages = [
|
||||||
pkgs.git
|
pkgs.git
|
||||||
pkgs.just
|
pkgs.just
|
||||||
|
pkgs.watchexec
|
||||||
];
|
];
|
||||||
|
|
||||||
dotenv = {
|
dotenv = {
|
||||||
@@ -32,11 +33,7 @@
|
|||||||
exec = "bun run dev";
|
exec = "bun run dev";
|
||||||
cwd = "./client";
|
cwd = "./client";
|
||||||
};
|
};
|
||||||
backend.exec = "just backend";
|
backend.exec = "just dev-back";
|
||||||
};
|
|
||||||
|
|
||||||
tasks = {
|
|
||||||
"backend:build".exec = "just clean && just build";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
services = {
|
services = {
|
||||||
|
|||||||
3
justfile
3
justfile
@@ -34,5 +34,8 @@ run-back:
|
|||||||
test-back:
|
test-back:
|
||||||
cd {{ output_dir }} && CONFIG_PATH={{ output_dir }} GO_ENV=test go test -C .. ./...
|
cd {{ output_dir }} && CONFIG_PATH={{ output_dir }} GO_ENV=test go test -C .. ./...
|
||||||
|
|
||||||
|
dev-back: clean
|
||||||
|
watchexec -r -e go,yaml,tpl -i '.devenv/**' -i '.direnv/**' -i 'client/**' -i 'vendor/**' 'go build -o {{ join(output_dir, "nixcn-cms") }} . && cd {{ output_dir }} && CONFIG_PATH={{ output_dir }} {{ exec_path }}'
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
devenv up --verbose
|
devenv up --verbose
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package magiclink
|
package authcode
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
@@ -19,7 +19,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Generate magic token
|
// Generate magic token
|
||||||
func NewMagicToken(email string) (string, error) {
|
func NewAuthCode(email string) (string, error) {
|
||||||
b := make([]byte, 32)
|
b := make([]byte, 32)
|
||||||
if _, err := rand.Read(b); err != nil {
|
if _, err := rand.Read(b); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -36,7 +36,7 @@ func NewMagicToken(email string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify magic token
|
// Verify magic token
|
||||||
func VerifyMagicToken(token string) (string, bool) {
|
func VerifyAuthCode(token string) (string, bool) {
|
||||||
val, ok := store.Load(token)
|
val, ok := store.Load(token)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", false
|
return "", false
|
||||||
@@ -3,6 +3,8 @@ package auth
|
|||||||
import "github.com/gin-gonic/gin"
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
func Handler(r *gin.RouterGroup) {
|
func Handler(r *gin.RouterGroup) {
|
||||||
r.POST("/magic", RequestMagicLink)
|
r.GET("/redirect", Redirect)
|
||||||
|
r.POST("/magic", Magic)
|
||||||
r.POST("/refresh", Refresh)
|
r.POST("/refresh", Refresh)
|
||||||
|
r.POST("/token", Token)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"nixcn-cms/data"
|
"nixcn-cms/data"
|
||||||
"nixcn-cms/internal/cryptography"
|
"nixcn-cms/internal/cryptography"
|
||||||
|
"nixcn-cms/pkgs/authcode"
|
||||||
"nixcn-cms/pkgs/email"
|
"nixcn-cms/pkgs/email"
|
||||||
"nixcn-cms/pkgs/magiclink"
|
|
||||||
"nixcn-cms/pkgs/turnstile"
|
"nixcn-cms/pkgs/turnstile"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -15,14 +15,17 @@ import (
|
|||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MagicLinkRequest struct {
|
type MagicRequest struct {
|
||||||
Email string `json:"email" binding:"required,email"`
|
ClientId string `json:"client_id"`
|
||||||
TurnstileToken string `json:"turnstile_token" binding:"required"`
|
RedirectUri string `json:"redirect_uri"`
|
||||||
|
State string `json:"state"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
TurnstileToken string `json:"turnstile_token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func RequestMagicLink(c *gin.Context) {
|
func Magic(c *gin.Context) {
|
||||||
// Parse request
|
// Parse request
|
||||||
var req MagicLinkRequest
|
var req MagicRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(400, gin.H{"error": "invalid request"})
|
c.JSON(400, gin.H{"error": "invalid request"})
|
||||||
return
|
return
|
||||||
@@ -35,18 +38,20 @@ func RequestMagicLink(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate magic token
|
code, err := authcode.NewAuthCode(req.Email)
|
||||||
token, err := magiclink.NewMagicToken(req.Email)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(500, gin.H{"error": "internal error"})
|
c.JSON(500, gin.H{"status": "code gen failed"})
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
link := viper.GetString("server.external_url") + "/login?ticket=" + token
|
uri := viper.GetString("server.external_url") +
|
||||||
|
"/api/v1/auth/redirect?" +
|
||||||
|
"code=" + code +
|
||||||
|
"&redirect_uri" + req.RedirectUri +
|
||||||
|
"&state" + req.State
|
||||||
|
|
||||||
debugMode := viper.GetString("server.debug_mode")
|
debugMode := viper.GetString("server.debug_mode")
|
||||||
if debugMode == "true" {
|
if debugMode == "true" {
|
||||||
log.Info("Magic link for " + req.Email + " : " + link)
|
log.Info("Magic link for " + req.Email + " : " + uri)
|
||||||
} else {
|
} else {
|
||||||
// Send email using resend
|
// Send email using resend
|
||||||
resend, err := email.NewResendClient()
|
resend, err := email.NewResendClient()
|
||||||
@@ -58,7 +63,7 @@ func RequestMagicLink(c *gin.Context) {
|
|||||||
resend.Send(
|
resend.Send(
|
||||||
req.Email,
|
req.Email,
|
||||||
"NixCN CMS Email Verify",
|
"NixCN CMS Email Verify",
|
||||||
"<p>Click the link below to verify your email. This link will expire in 10 minutes.</p><a href="+link+">"+link+"</a>",
|
"<p>Click the link below to verify your email. This link will expire in 10 minutes.</p><a href="+uri+">"+uri+"</a>",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +79,7 @@ func VerifyMagicLink(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify email token
|
// Verify email token
|
||||||
email, ok := magiclink.VerifyMagicToken(magicToken)
|
email, ok := authcode.VerifyAuthCode(magicToken)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.JSON(401, gin.H{"error": "invalid or expired token"})
|
c.JSON(401, gin.H{"error": "invalid or expired token"})
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1 +1,7 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
func Redirect(c *gin.Context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1 +1,7 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
func Token(c *gin.Context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user