add account page
This commit is contained in:
parent
aac72f2a9e
commit
ffe6dc1fea
12 changed files with 188 additions and 4 deletions
|
@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Added
|
### Added
|
||||||
- added Changelog
|
- added Changelog
|
||||||
- Ability to change the url and description of shortlinks
|
- Ability to change the url and description of shortlinks
|
||||||
|
- /api/v1/users/me endpoint to get user details
|
||||||
|
- Account page for user overview and later use
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- License changed to MIT
|
- License changed to MIT
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"codeberg.org/lauralani/go-urlsh/internal/db"
|
||||||
"codeberg.org/lauralani/go-urlsh/internal/misc"
|
"codeberg.org/lauralani/go-urlsh/internal/misc"
|
||||||
"codeberg.org/lauralani/go-urlsh/models"
|
"codeberg.org/lauralani/go-urlsh/models"
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"database/sql"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
@ -58,3 +60,69 @@ func HandleUserPost(c *fiber.Ctx) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func HandleUserMe(c *fiber.Ctx) error {
|
||||||
|
if !db.IsCookieValid(c.Cookies(misc.CookieName, "")) && !db.IsApiKeyValid(c.GetRespHeader("x-api-key", "")) {
|
||||||
|
return fiber.NewError(fiber.StatusUnauthorized, "401 Unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie := c.Cookies(misc.CookieName, "")
|
||||||
|
apikey := c.GetRespHeader("x-api-key", "")
|
||||||
|
|
||||||
|
var authmethod string
|
||||||
|
var user models.User
|
||||||
|
|
||||||
|
if cookie != "" {
|
||||||
|
authmethod = "cookie"
|
||||||
|
} else {
|
||||||
|
authmethod = "apikey"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch authmethod {
|
||||||
|
case "cookie":
|
||||||
|
var session models.Session
|
||||||
|
err := models.DB.NewSelect().Model(&session).Where("cookie = ?", cookie).Scan(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
log.Printf("[HandleUserMe] Session %v not found\n", cookie)
|
||||||
|
} else {
|
||||||
|
log.Printf("[HandleUserMe] Error querying session %v from database: %v\n", cookie, err)
|
||||||
|
}
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = models.DB.NewSelect().Model(&user).Where("username = ?", session.UserName).Scan(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[HandleUserMe] Error querying user %v from database: %v\n", session.UserName, err)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
case "apikey":
|
||||||
|
var key models.ApiKey
|
||||||
|
err := models.DB.NewSelect().Model(&key).Where("key = ?", apikey).Scan(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
log.Printf("[HandleUserMe] ApiKey %v not found\n", apikey)
|
||||||
|
} else {
|
||||||
|
log.Printf("[HandleUserMe] Error querying apikey %v from database: %v\n", apikey, err)
|
||||||
|
}
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = models.DB.NewSelect().Model(&user).Where("username = ?", key.UserName).Scan(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[HandleUserMe] Error querying user %v from database: %v\n", key.UserName, err)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var userresponse = new(models.UserResponse)
|
||||||
|
userresponse.UserName = user.UserName
|
||||||
|
userresponse.Created = user.Created
|
||||||
|
|
||||||
|
err := c.JSON(userresponse)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -53,6 +53,8 @@ func SetupFiber() error {
|
||||||
|
|
||||||
fiberapp.Get("/admin/", web.HandleAdminLinkIndexGet)
|
fiberapp.Get("/admin/", web.HandleAdminLinkIndexGet)
|
||||||
|
|
||||||
|
fiberapp.Get("/admin/account/", web.HandleAdminAccountGet)
|
||||||
|
|
||||||
fiberapp.Get("/admin/login", web.HandleAdminLoginGet)
|
fiberapp.Get("/admin/login", web.HandleAdminLoginGet)
|
||||||
fiberapp.Post("/admin/login", web.HandleAdminLoginPost)
|
fiberapp.Post("/admin/login", web.HandleAdminLoginPost)
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ func IsCookieValid(val string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err := models.DB.NewSelect().Model((*models.Login)(nil)).Where("cookie = ?", val).Count(context.Background())
|
count, err := models.DB.NewSelect().Model((*models.Session)(nil)).Where("cookie = ?", val).Count(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error checking cookie validity for cookie %v\n", val)
|
log.Printf("Error checking cookie validity for cookie %v\n", val)
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -25,7 +25,7 @@ func InitializeDB() error {
|
||||||
return fmt.Errorf("couldn't create database: [%w]", err)
|
return fmt.Errorf("couldn't create database: [%w]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = models.DB.NewCreateTable().IfNotExists().Model((*models.Login)(nil)).Exec(context.Background())
|
_, err = models.DB.NewCreateTable().IfNotExists().Model((*models.Session)(nil)).Exec(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("couldn't create database: [%w]", err)
|
return fmt.Errorf("couldn't create database: [%w]", err)
|
||||||
}
|
}
|
||||||
|
|
41
internal/web/account.go
Normal file
41
internal/web/account.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"codeberg.org/lauralani/go-urlsh/internal/db"
|
||||||
|
"codeberg.org/lauralani/go-urlsh/internal/misc"
|
||||||
|
"codeberg.org/lauralani/go-urlsh/models"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleAdminAccountGet(c *fiber.Ctx) error {
|
||||||
|
if !db.IsCookieValid(c.Cookies(misc.CookieName, "")) {
|
||||||
|
c.Location("/admin/")
|
||||||
|
c.Status(fiber.StatusSeeOther)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie := c.Cookies(misc.CookieName, "")
|
||||||
|
var session models.Session
|
||||||
|
var user models.User
|
||||||
|
|
||||||
|
err := models.DB.NewSelect().Model(&session).Where("cookie = ?", cookie).Scan(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
log.Printf("[HandleUserMe] Session %v not found\n", cookie)
|
||||||
|
} else {
|
||||||
|
log.Printf("[HandleUserMe] Error querying session %v from database: %v\n", cookie, err)
|
||||||
|
}
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = models.DB.NewSelect().Model(&user).Where("username = ?", session.UserName).Scan(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[HandleUserMe] Error querying user %v from database: %v\n", session.UserName, err)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Render("account", user)
|
||||||
|
}
|
|
@ -41,7 +41,7 @@ func HandleAdminLoginPost(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
key := uuid.New().String()
|
key := uuid.New().String()
|
||||||
dblogin := new(models.Login)
|
dblogin := new(models.Session)
|
||||||
|
|
||||||
cookie := new(fiber.Cookie)
|
cookie := new(fiber.Cookie)
|
||||||
cookie.Name = misc.CookieName
|
cookie.Name = misc.CookieName
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Login struct {
|
type Session struct {
|
||||||
bun.BaseModel `bun:"table:logins"`
|
bun.BaseModel `bun:"table:logins"`
|
||||||
Cookie string `bun:"cookie,pk,type:uuid"`
|
Cookie string `bun:"cookie,pk,type:uuid"`
|
||||||
UserName string `bun:"username,notnull"`
|
UserName string `bun:"username,notnull"`
|
||||||
|
|
62
views/account.tmpl
Normal file
62
views/account.tmpl
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset='utf-8'>
|
||||||
|
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
|
||||||
|
<title>Account - go-urlsh</title>
|
||||||
|
<meta name='viewport' content='width=device-width, initial-scale=1'>
|
||||||
|
<link rel="stylesheet" href="/admin/pico.min.css">
|
||||||
|
<link rel="stylesheet" href="/admin/custom.css">
|
||||||
|
<script src="/admin/main.js" defer></script>
|
||||||
|
<style>
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<nav class="container-fluid">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/admin/" class="contrast"><strong>go-urlsh</strong></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/admin/">Links</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/admin/apikeys/">API Keys</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="javascript: void(0)" >Users (coming soon)</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="javascript: Logout()" style="color: red;">Logout</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<main class="container" style="padding: 0">
|
||||||
|
<h1>Account</h1>
|
||||||
|
<h2>User Details</h2>
|
||||||
|
|
||||||
|
<p>Username: {{ .UserName }}</p>
|
||||||
|
<p>Created at: {{ .Created }}</p>
|
||||||
|
|
||||||
|
<h2>Security</h2>
|
||||||
|
<p>nothing here yet</p>
|
||||||
|
<dialog id="dialog-info">
|
||||||
|
<h2 id="dialog-heading"></h2>
|
||||||
|
<p id="dialog-text"></p>
|
||||||
|
<form method="dialog">
|
||||||
|
<button>Close</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
</main>
|
||||||
|
{{template "partials/footer" .}}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -23,6 +23,9 @@
|
||||||
<li>
|
<li>
|
||||||
<a href="/admin/">Links</a>
|
<a href="/admin/">Links</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/admin/account/">Account</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/admin/apikeys/">API Keys</a>
|
<a href="/admin/apikeys/">API Keys</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -23,6 +23,9 @@
|
||||||
<li>
|
<li>
|
||||||
<a href="/admin/">Links</a>
|
<a href="/admin/">Links</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/admin/account/">Account</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/admin/apikeys/">API Keys</a>
|
<a href="/admin/apikeys/">API Keys</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -29,6 +29,9 @@
|
||||||
<li>
|
<li>
|
||||||
<a href="/admin/links/new" style="color: greenyellow;">Add new Shortlink</a>
|
<a href="/admin/links/new" style="color: greenyellow;">Add new Shortlink</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/admin/account/">Account</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/admin/apikeys/">API Keys</a>
|
<a href="/admin/apikeys/">API Keys</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
Loading…
Reference in a new issue