diff --git a/app.go b/app.go
index 1000fdc..52d9581 100644
--- a/app.go
+++ b/app.go
@@ -1,8 +1,10 @@
package main
import (
+ "errors"
"fmt"
"os"
+ "syscall"
"git.wh64.net/devproje/kuma-archive/config"
"git.wh64.net/devproje/kuma-archive/internal/routes"
@@ -11,6 +13,7 @@ import (
"github.com/devproje/commando/option"
"github.com/devproje/commando/types"
"github.com/gin-gonic/gin"
+ "golang.org/x/term"
)
var (
@@ -40,6 +43,9 @@ func main() {
gin.SetMode(gin.ReleaseMode)
}
+ // init auth module
+ service.NewAuthService()
+
gin := gin.Default()
routes.New(gin, ver, apiOnly)
@@ -65,6 +71,45 @@ func main() {
return nil
})
+ command.ComplexRoot("account", "file server account manager", []commando.Node{
+ command.Then("create", "create account", func(n *commando.Node) error {
+ var username, password string
+
+ fmt.Print("new username: ")
+ if _, err := fmt.Scanln(&username); err != nil {
+ return fmt.Errorf("failed to read username: %v", err)
+ }
+
+ fmt.Print("new password: ")
+ bytePassword, err := term.ReadPassword(int(syscall.Stdin))
+ if err != nil {
+ return fmt.Errorf("failed to read password: %v", err)
+ }
+ password = string(bytePassword)
+ fmt.Println()
+
+ fmt.Print("type new password one more time: ")
+ checkByte, err := term.ReadPassword(int(syscall.Stdin))
+ if err != nil {
+ return fmt.Errorf("failed to read password: %v", err)
+ }
+ check := string(checkByte)
+ fmt.Println()
+
+ if password != check {
+ return errors.New("password check is not correct")
+ }
+
+ auth := service.NewAuthService()
+ if err := auth.Create(&service.Account{Username: username, Password: password}); err != nil {
+ return err
+ }
+
+ fmt.Printf("Account for %s created successfully\n", username)
+ return nil
+ }),
+ })
+
if err := command.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
diff --git a/go.mod b/go.mod
index d6742ea..2ea1d02 100644
--- a/go.mod
+++ b/go.mod
@@ -33,6 +33,7 @@ require (
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/net v0.37.0 // indirect
golang.org/x/sys v0.31.0 // indirect
+ golang.org/x/term v0.30.0 // indirect
golang.org/x/text v0.23.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/go.sum b/go.sum
index 6256d51..10109e1 100644
--- a/go.sum
+++ b/go.sum
@@ -85,6 +85,8 @@ golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
+golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
diff --git a/internal/routes/mod.go b/internal/routes/mod.go
index f9531d4..75aa8d1 100644
--- a/internal/routes/mod.go
+++ b/internal/routes/mod.go
@@ -106,6 +106,38 @@ func New(app *gin.Engine, version *service.Version, apiOnly bool) {
ctx.FileAttachment(data.Path, data.Name)
})
+ auth := api.Group("/auth")
+ {
+ auth.POST("/login", func(ctx *gin.Context) {
+ auth := service.NewAuthService()
+ username := ctx.PostForm("username")
+ password := ctx.PostForm("password")
+
+ acc, err := auth.Read(username)
+ if err != nil {
+ ctx.JSON(401, gin.H{
+ "ok": 0,
+ "errno": "username or password not invalid",
+ })
+ return
+ }
+
+ ok, err := auth.Verify(username, password)
+ if err != nil || !ok {
+ ctx.JSON(401, gin.H{
+ "ok": 0,
+ "errno": "username or password not invalid",
+ })
+ return
+ }
+
+ ctx.JSON(200, gin.H{
+ "ok": 1,
+ "token": auth.Token(acc.Username, acc.Password),
+ })
+ })
+ }
+
api.GET("/version", func(ctx *gin.Context) {
ctx.String(200, "%s", version.String())
})
diff --git a/internal/service/auth.go b/internal/service/auth.go
new file mode 100644
index 0000000..f53af92
--- /dev/null
+++ b/internal/service/auth.go
@@ -0,0 +1,168 @@
+package service
+
+import (
+ "crypto/rand"
+ "crypto/sha256"
+ "encoding/base64"
+ "encoding/hex"
+ "fmt"
+ "os"
+ "strings"
+)
+
+type AuthService struct{}
+
+type Account struct {
+ Username string
+ Password string
+ Salt string
+}
+
+func NewAuthService() *AuthService {
+ return &AuthService{}
+}
+
+func init() {
+ db, err := Open()
+ if err != nil {
+ _, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
+ return
+ }
+ defer db.Close()
+
+ stmt, err := db.Prepare(strings.TrimSpace(`
+ create table Account(
+ username varchar(25),
+ password varchar(255),
+ salt varchar(50),
+ primary key (username)
+ );
+ `))
+ if err != nil {
+ _, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
+ return
+ }
+ defer stmt.Close()
+
+ if _, err = stmt.Exec(); err != nil {
+ _, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
+ return
+ }
+}
+
+func (s *AuthService) Create(data *Account) error {
+ db, err := Open()
+ if err != nil {
+ return err
+ }
+ defer db.Close()
+
+ stmt, err := db.Prepare("insert into Account values(?, ?, ?);")
+ if err != nil {
+ return err
+ }
+ defer stmt.Close()
+
+ salt := genSalt()
+ if _, err = stmt.Exec(data.Username, encrypt(data.Password, salt), salt); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (s *AuthService) Read(username string) (*Account, error) {
+ db, err := Open()
+ if err != nil {
+ return nil, err
+ }
+ defer db.Close()
+
+ stmt, err := db.Prepare("select * from Account where username = ?;")
+ if err != nil {
+ return nil, err
+ }
+ defer stmt.Close()
+
+ var account Account
+ if err := stmt.QueryRow(username).Scan(&account.Username, &account.Password, &account.Salt); err != nil {
+ return nil, err
+ }
+
+ return &account, nil
+}
+
+func (s *AuthService) Update(username, password string) error {
+ db, err := Open()
+ if err != nil {
+ return err
+ }
+ defer db.Close()
+
+ stmt, err := db.Prepare("update Account set password = ?, salt = ? where username = ?;")
+ if err != nil {
+ return err
+ }
+ defer stmt.Close()
+
+ salt := genSalt()
+ if _, err = stmt.Exec(encrypt(password, salt), salt, username); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (s *AuthService) Delete(username string) error {
+ db, err := Open()
+ if err != nil {
+ return err
+ }
+ defer db.Close()
+
+ stmt, err := db.Prepare("delete from Account where username = ?;")
+ if err != nil {
+ return err
+ }
+ defer stmt.Close()
+
+ if _, err = stmt.Exec(username); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (s *AuthService) Verify(username, password string) (bool, error) {
+ account, err := s.Read(username)
+ if err != nil {
+ return false, err
+ }
+
+ if encrypt(password, account.Salt) == account.Password {
+ return true, nil
+ }
+
+ return false, nil
+}
+
+func (s *AuthService) Token(username, password string) string {
+ raw := fmt.Sprintf("%s:%s", username, password)
+ return base64.StdEncoding.EncodeToString([]byte(raw))
+}
+
+func encrypt(password, salt string) string {
+ hash := sha256.New()
+ hash.Write([]byte(password + salt))
+ return hex.EncodeToString(hash.Sum(nil))
+}
+
+func genSalt() string {
+ b := make([]byte, 16)
+ _, err := rand.Read(b)
+ if err != nil {
+ _, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
+ return ""
+ }
+ return hex.EncodeToString(b)
+}
diff --git a/internal/service/database.go b/internal/service/database.go
index 482c78d..35bc4f2 100644
--- a/internal/service/database.go
+++ b/internal/service/database.go
@@ -2,11 +2,12 @@ package service
import (
"database/sql"
+ "path/filepath"
"git.wh64.net/devproje/kuma-archive/config"
_ "github.com/mattn/go-sqlite3"
)
func Open() (*sql.DB, error) {
- return sql.Open("sqlite3", (config.ROOT_DIRECTORY))
+ return sql.Open("sqlite3", filepath.Join(config.ROOT_DIRECTORY, "data.db"))
}
diff --git a/src/App.scss b/src/App.scss
index a68a4b2..487d400 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -31,9 +31,36 @@
align-items: center;
flex-direction: row;
justify-content: center;
+
.link {
margin-left: 0.5rem;
}
+
+ .login-info {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ span {
+ margin-left: 10px;
+ }
+ }
+
+ .login-btn {
+ display: flex;
+ margin-left: 0.5rem;
+ align-items: center;
+ border-radius: 25px;
+ padding: 0.45rem 1rem;
+ justify-content: center;
+ transition-duration: 300ms;
+ background-color: var(--nav-color);
+
+ &:hover {
+ transition-duration: 300ms;
+ background-color: var(--nav-hover);
+ }
+ }
}
}
diff --git a/src/App.tsx b/src/App.tsx
index 9fe5db5..036bddf 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -9,18 +9,33 @@ import { BrowserRouter, Route, Routes, useLocation } from "react-router";
import "./App.scss";
import kuma from "./assets/kuma.png";
import NotFound from "./components/notfound";
+import Login from "./components/login";
+import { useAuthStore } from "./store/auth";
+import Logout from "./components/logout";
function App() {
return (