nim/internal/handlers/admin.go
2025-06-01 21:20:08 +08:00

213 lines
6.1 KiB
Go

package handlers
import (
"bytes"
"encoding/base64"
"image/png"
"log"
"os"
"time"
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/pquerna/otp/totp"
"golang.org/x/crypto/scrypt"
"nim.jasinco.work/app/internal"
"nim.jasinco.work/app/nimdb"
)
type NewAdmin struct {
Name string `json:"name"`
Password string `json:"password"`
TOTP_Secret string `json:"totp_secret"`
}
type AdminAuth struct {
Name string `json:"name"`
Password string `json:"password"`
TOTP_code string `json:"totp_code"`
}
func Admin_create(c *fiber.Ctx) error {
cfg := new(NewAdmin)
if err := c.BodyParser(cfg); err != nil {
return fiber.ErrBadRequest
}
hashed, err := scrypt.Key([]byte(cfg.Password), []byte(internal.SALT), 32768, 8, 1, 32)
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
err = internal.NIMDB.AdminCreateAccount(c.Context(), nimdb.AdminCreateAccountParams{Username: cfg.Name, Password: base64.StdEncoding.EncodeToString(hashed), Totp: cfg.TOTP_Secret})
if err != nil {
log.Println(base64.StdEncoding.EncodeToString(hashed), err)
return c.SendStatus(fiber.StatusBadRequest)
}
return c.SendStatus(fiber.StatusCreated)
}
func Admin_new_totp_code(c *fiber.Ctx) error {
name := c.Query("name")
if len(name) == 0 {
return fiber.ErrBadRequest
}
key, err := totp.Generate(totp.GenerateOpts{Issuer: "TCIVS_NIMING", AccountName: name})
if err != nil {
return fiber.ErrInternalServerError
}
var buf bytes.Buffer
img, err := key.Image(200, 200)
png.Encode(&buf, img)
return c.JSON(fiber.Map{"key": key.Secret(), "img": base64.StdEncoding.EncodeToString(buf.Bytes())})
}
func Admin_Login(c *fiber.Ctx) error {
cred := new(AdminAuth)
if err := c.BodyParser(cred); err != nil {
return fiber.ErrBadRequest
}
hashed, err := scrypt.Key([]byte(cred.Password), []byte(internal.SALT), 32768, 8, 1, 32)
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
rec, err := internal.NIMDB.AdminLoginGetTOTP(c.Context(), nimdb.AdminLoginGetTOTPParams{Username: cred.Name, Password: base64.StdEncoding.EncodeToString(hashed)})
if err != nil {
log.Println(err)
return c.SendStatus(fiber.StatusUnauthorized)
}
if !totp.Validate(cred.TOTP_code, rec.Totp) {
log.Println(totp.GenerateCode(rec.Totp, time.Now()))
return c.SendStatus(fiber.StatusUnauthorized)
}
claims := jwt.MapClaims{"name": cred.Name, "admin": rec.Super}
token := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
t, err := token.SignedString([]byte(internal.JWT_SECRET))
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
cookie := new(fiber.Cookie)
cookie.Name = "token"
cookie.Value = t
cookie.Expires = time.Now().Add(5 * time.Hour)
c.Cookie(cookie)
return c.JSON(fiber.Map{"token": t})
}
func Admin_Fetch_Post(c *fiber.Ctx) error {
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
super := claims["admin"].(bool)
ctx := c.Context()
if !super {
rec, err := internal.NIMDB.AdminGetPost(ctx)
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
type recwithimg struct {
Id int32 `json:"id"`
Content string `json:"content"`
Signing pgtype.Text `json:"signing"`
Post_at time.Time `json:"post_at"`
Enclosue []string `json:"enclosure"`
}
rec_with_img := make([]recwithimg, len(rec))
for id, k := range rec {
rec_with_img[id] = recwithimg{Id: k.ID, Content: k.Content, Signing: k.Signing, Post_at: k.PostAt.Time}
imgrec, err := internal.NIMDB.AdminGetMedia(ctx, pgtype.Int4{Int32: k.ID, Valid: true})
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
rec_with_img[id].Enclosue = imgrec
}
return c.JSON(rec_with_img)
} else {
rec, err := internal.NIMDB.SuperAdminGetPost(ctx)
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
type recwithimg struct {
Id int32 `json:"id"`
Content string `json:"content"`
Signing pgtype.Text `json:"signing"`
Post_at time.Time `json:"post_at"`
Enclosue []string `json:"enclosure"`
Phase nimdb.PostPhase `json:"phase"`
}
rec_with_img := make([]recwithimg, len(rec))
for id, k := range rec {
rec_with_img[id] = recwithimg{Id: k.ID, Content: k.Content, Signing: k.Signing, Post_at: k.PostAt.Time, Phase: k.Phase}
imgrec, err := internal.NIMDB.AdminGetMedia(ctx, pgtype.Int4{Int32: k.ID, Valid: true})
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
rec_with_img[id].Enclosue = imgrec
}
return c.JSON(rec_with_img)
}
}
type Verify struct {
Id int32 `json:"post"`
Check bool `json:"check"`
}
func AdminVerify(c *fiber.Ctx) error {
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
super := claims["admin"].(bool)
ctx := c.Context()
tx, err := internal.POOL.Begin(ctx)
defer tx.Rollback(ctx)
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
qtx := internal.NIMDB.WithTx(tx)
post_verify := new(Verify)
log.Println(string(c.Body()))
if err = c.BodyParser(post_verify); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
if !super {
if post_verify.Check {
_, err = qtx.AdminVerify(ctx, nimdb.AdminVerifyParams{ID: post_verify.Id, Phase: nimdb.PostPhaseOk})
} else {
_, err = qtx.AdminVerify(ctx, nimdb.AdminVerifyParams{ID: post_verify.Id, Phase: nimdb.PostPhaseRejected})
}
if err != nil {
log.Println(err.Error())
return c.SendStatus(fiber.StatusBadRequest)
}
} else {
if post_verify.Check {
_, err = qtx.SuperAdminVerify(ctx, nimdb.SuperAdminVerifyParams{ID: post_verify.Id, Phase: nimdb.PostPhaseOk})
} else {
_, err = qtx.SuperAdminVerify(ctx, nimdb.SuperAdminVerifyParams{ID: post_verify.Id, Phase: nimdb.PostPhaseAdminRejected})
}
if err != nil {
log.Println(err.Error())
return c.SendStatus(fiber.StatusBadRequest)
}
}
err = qtx.AdminUpdateImage(ctx, pgtype.Int4{Int32: post_verify.Id, Valid: true})
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
return tx.Commit(ctx)
}