feat: login screen
This commit is contained in:
parent
fede2c3f8e
commit
fe982dd4ca
2
go.mod
2
go.mod
@ -8,6 +8,8 @@ require (
|
||||
gorm.io/gorm v1.25.12
|
||||
)
|
||||
|
||||
require github.com/go-resty/resty/v2 v2.15.3 // indirect
|
||||
|
||||
require (
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
|
2
go.sum
2
go.sum
@ -5,6 +5,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/go-resty/resty/v2 v2.15.3 h1:bqff+hcqAflpiF591hhJzNdkRsFhlB96CYfBwSFvql8=
|
||||
github.com/go-resty/resty/v2 v2.15.3/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
|
@ -1,3 +1,9 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
font-family: "Atkinson Hyperlegible", sans-serif;
|
||||
}
|
||||
|
||||
|
||||
|
@ -7,13 +7,6 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Represents a User in the Database
|
||||
type User struct {
|
||||
gorm.Model
|
||||
Name string
|
||||
Password string
|
||||
}
|
||||
|
||||
// A package-level database handle.
|
||||
// It is set up by the [SetupSchema] func,
|
||||
// which should be called before the webserver
|
||||
@ -23,8 +16,6 @@ var db *gorm.DB
|
||||
// Creates all the database tables that this module requires,
|
||||
// and sets up the database handle for this module
|
||||
func SetupSchema(dbHandle *gorm.DB) {
|
||||
dbHandle.AutoMigrate(&User{})
|
||||
db = dbHandle
|
||||
}
|
||||
|
||||
// Registers all the routes that this module (auth) provides
|
||||
|
59
src/modules/index/index.go
Normal file
59
src/modules/index/index.go
Normal file
@ -0,0 +1,59 @@
|
||||
package index
|
||||
|
||||
import (
|
||||
"acide/src/utils"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
// Registers all the routes that this module (auth) provides
|
||||
func SetupRoutes(g *echo.Group) {
|
||||
log.Print("Setting up the index module")
|
||||
|
||||
// To include custom rendering logic:
|
||||
g.GET("/", index)
|
||||
g.POST("/f/login", loginFragment)
|
||||
}
|
||||
|
||||
func index(c echo.Context) error {
|
||||
return utils.RenderTempl(c, http.StatusOK, IndexTempl())
|
||||
}
|
||||
|
||||
func loginFragment(c echo.Context) error {
|
||||
|
||||
navidromeServer := c.FormValue("navidrome-url")
|
||||
username := c.FormValue("username")
|
||||
password := c.FormValue("password")
|
||||
|
||||
// TODO: validation
|
||||
|
||||
sessionToken, err := login(navidromeServer, username, password)
|
||||
if err != nil {
|
||||
errorMessage := fmt.Sprintf("<div class='bg-red-500 text-white p-2 m-2 rounded'>Error logging in: %s</div>", err)
|
||||
return c.HTML(http.StatusBadRequest, errorMessage)
|
||||
}
|
||||
|
||||
cookie1 := new(http.Cookie)
|
||||
cookie1.Name = "session-token"
|
||||
cookie1.Value = sessionToken
|
||||
cookie1.Expires = time.Now().Add(24 * time.Hour)
|
||||
cookie1.Path = "/"
|
||||
cookie1.HttpOnly = true
|
||||
cookie1.Secure = true
|
||||
c.SetCookie(cookie1)
|
||||
|
||||
cookie2 := new(http.Cookie)
|
||||
cookie2.Name = "navidrome-url"
|
||||
cookie2.Value = navidromeServer
|
||||
cookie2.Expires = time.Now().Add(24 * time.Hour)
|
||||
cookie2.Path = "/"
|
||||
cookie2.HttpOnly = true
|
||||
cookie2.Secure = true
|
||||
c.SetCookie(cookie2)
|
||||
|
||||
return c.HTML(http.StatusOK, "wrote some cookies :D")
|
||||
}
|
64
src/modules/index/index.templ
Normal file
64
src/modules/index/index.templ
Normal file
@ -0,0 +1,64 @@
|
||||
package index
|
||||
|
||||
import "acide/src/utils"
|
||||
|
||||
templ IndexTempl() {
|
||||
@utils.SkeletonTempl() {
|
||||
<nav class="bg-sky-500 text-white text-xl font-bold p-2">
|
||||
music to go
|
||||
</nav>
|
||||
<div hx-ext="response-targets">
|
||||
Login to Navidrome:
|
||||
<br/>
|
||||
<form
|
||||
hx-post="/f/login"
|
||||
hx-swap="innerHTML"
|
||||
hx-target="#login-result"
|
||||
hx-target-400="#login-result"
|
||||
hx-target-500="#login-result"
|
||||
>
|
||||
<label for="navidrome-url" class="text-sm">
|
||||
Navidrome URL:
|
||||
</label>
|
||||
<input
|
||||
id="navidrome-url"
|
||||
class="w-full py-1 px-2 rounded"
|
||||
placeholder="https://"
|
||||
name="navidrome-url"
|
||||
required
|
||||
/>
|
||||
<br/>
|
||||
<label for="username" class="text-sm">
|
||||
Username:
|
||||
</label>
|
||||
<input
|
||||
id="username"
|
||||
class="w-full py-1 px-2 rounded"
|
||||
placeholder="username"
|
||||
name="username"
|
||||
required
|
||||
/>
|
||||
<br/>
|
||||
<label for="password" class="text-sm">
|
||||
Password:
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
class="w-full py-1 px-2 rounded"
|
||||
placeholder="password"
|
||||
name="password"
|
||||
type="password"
|
||||
required
|
||||
/>
|
||||
<br/>
|
||||
<button class="py-2 px-4 my-2 bg-sky-500 text-white rounded" type="submit">
|
||||
Log-in
|
||||
</button>
|
||||
<br/>
|
||||
<div id="login-result">
|
||||
Result:
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
}
|
60
src/modules/index/index_templ.go
Normal file
60
src/modules/index/index_templ.go
Normal file
@ -0,0 +1,60 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.778
|
||||
package index
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import "acide/src/utils"
|
||||
|
||||
func IndexTempl() templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<nav class=\"bg-sky-500 text-white text-xl font-bold p-2\">music to go</nav><div hx-ext=\"response-targets\">Login to Navidrome:<br><form hx-post=\"/f/login\" hx-swap=\"innerHTML\" hx-target=\"#login-result\" hx-target-400=\"#login-result\" hx-target-500=\"#login-result\"><label for=\"navidrome-url\" class=\"text-sm\">Navidrome URL:</label> <input id=\"navidrome-url\" class=\"w-full py-1 px-2 rounded\" placeholder=\"https://\" name=\"navidrome-url\" required><br><label for=\"username\" class=\"text-sm\">Username:</label> <input id=\"username\" class=\"w-full py-1 px-2 rounded\" placeholder=\"username\" name=\"username\" required><br><label for=\"password\" class=\"text-sm\">Password:</label> <input id=\"password\" class=\"w-full py-1 px-2 rounded\" placeholder=\"password\" name=\"password\" type=\"password\" required><br><button class=\"py-2 px-4 my-2 bg-sky-500 text-white rounded\" type=\"submit\">Log-in</button><br><div id=\"login-result\">Result:</div></form></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
templ_7745c5c3_Err = utils.SkeletonTempl().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
41
src/modules/index/login.go
Normal file
41
src/modules/index/login.go
Normal file
@ -0,0 +1,41 @@
|
||||
package index
|
||||
|
||||
import (
|
||||
"acide/src/utils"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type AuthError struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// Attempts to login to a navidrome server with the provided credentials.
|
||||
// Returns the session key if succesful, an error otherwise
|
||||
func login(server, username, password string) (string, error) {
|
||||
client := resty.New()
|
||||
|
||||
var loginData utils.AuthSuccess
|
||||
var loginError AuthError
|
||||
|
||||
response, err := client.R().
|
||||
SetHeader("Content-Type", "").
|
||||
SetBody(fmt.Sprintf(`{"username":"%s","password":"%s"}`, username, password)).
|
||||
SetResult(&loginData).
|
||||
SetError(&loginError).
|
||||
Post(fmt.Sprintf("%s/auth/login", server))
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !response.IsSuccess() {
|
||||
return "", errors.New(loginError.Error)
|
||||
}
|
||||
|
||||
var sessionToken = loginData.Token
|
||||
|
||||
return sessionToken, nil
|
||||
}
|
@ -2,6 +2,7 @@ package src
|
||||
|
||||
import (
|
||||
"acide/src/modules/auth"
|
||||
"acide/src/modules/index"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
@ -9,7 +10,7 @@ import (
|
||||
"github.com/labstack/echo/middleware"
|
||||
)
|
||||
|
||||
var dev = os.Getenv("DEV") != ""
|
||||
var dev = os.Getenv("APP_ENV") == "dev"
|
||||
|
||||
// Sets up the Echo server, and registers all routes and sub routes
|
||||
func (s *Server) RegisterRoutes() http.Handler {
|
||||
@ -23,14 +24,10 @@ func (s *Server) RegisterRoutes() http.Handler {
|
||||
http.FileServer(http.Dir("public"))))
|
||||
e.GET("/public/*", echo.WrapHandler(staticFilesFolder))
|
||||
|
||||
// TODO: Register subroutes here
|
||||
// login.SetupRoutes(e.Group("/auth"))
|
||||
// NOTE: Register subroutes here
|
||||
index.SetupRoutes(e.Group(""))
|
||||
auth.SetupRoutes(e.Group("/auth"))
|
||||
|
||||
e.GET("/", func(ctx echo.Context) error {
|
||||
return ctx.String(http.StatusOK, "Hello")
|
||||
})
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"acide/src/database"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
@ -9,13 +8,10 @@ import (
|
||||
"time"
|
||||
|
||||
_ "github.com/joho/godotenv/autoload"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
port int
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// Creates a new Server
|
||||
@ -28,7 +24,6 @@ func NewServer() *http.Server {
|
||||
|
||||
NewServer := &Server{
|
||||
port: port,
|
||||
db: database.New(),
|
||||
}
|
||||
|
||||
// TODO: Register DB schemas here
|
||||
|
12
src/utils/types.go
Normal file
12
src/utils/types.go
Normal file
@ -0,0 +1,12 @@
|
||||
package utils
|
||||
|
||||
// The result of a Login
|
||||
type AuthSuccess struct {
|
||||
Id string `json:"id"`
|
||||
IsAdmin bool `json:"isAdmin"`
|
||||
Name string `json:"name"`
|
||||
SubsonicSalt string `json:"subsonicSalt"`
|
||||
SubsonicToken string `json:"subsonicToken"`
|
||||
Token string `json:"token"`
|
||||
Username string `json:"username"`
|
||||
}
|
@ -7,7 +7,13 @@ templ SkeletonTempl() {
|
||||
<meta charset="utf-8"/>
|
||||
<title>Acide Template</title>
|
||||
<link href="/public/css/output.css" rel="stylesheet"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet"/>
|
||||
<script src="/public/js/htmx.min.js" defer></script>
|
||||
<script src="/public/js/_hyperscript.min.js" defer></script>
|
||||
<script src="https://unpkg.com/htmx-ext-response-targets@2.0.0/response-targets.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
|
@ -29,7 +29,7 @@ func SkeletonTempl() templ.Component {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"es\"><head><meta charset=\"utf-8\"><title>Acide Template</title><link href=\"/public/css/output.css\" rel=\"stylesheet\"><script src=\"/public/js/htmx.min.js\" defer></script></head><body><main>")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"es\"><head><meta charset=\"utf-8\"><title>Acide Template</title><link href=\"/public/css/output.css\" rel=\"stylesheet\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link rel=\"preconnect\" href=\"https://fonts.googleapis.com\"><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin><link href=\"https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400;1,700&display=swap\" rel=\"stylesheet\"><script src=\"/public/js/htmx.min.js\" defer></script><script src=\"/public/js/_hyperscript.min.js\" defer></script><script src=\"https://unpkg.com/htmx-ext-response-targets@2.0.0/response-targets.js\" defer></script></head><body><main>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./src/**/*.{html,templ}",
|
||||
"./src/**/*.{html,templ,go}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
|
Loading…
Reference in New Issue
Block a user