This commit is contained in:
r8zavetr8v 2024-10-01 01:21:37 +03:00
parent cfb4940fe8
commit c7156f7aab
8 changed files with 190 additions and 57 deletions

View File

@ -1,10 +1,15 @@
services: services:
database: database:
image: postgres:16 image: postgres:17
container_name: draincloud-db container_name: draincloud-db
ports: ports:
- 5432:5432 - 5432:5432
environment: environment:
POSTGRES_USERNAME: draincloud POSTGRES_USERNAME: draincloud
POSTGRES_DB: draincloud POSTGRES_DB: draincloud
POSTGRES_PASSWORD: draincloud.dev.secret POSTGRES_PASSWORD: draincloud.dev.secret
volumes:
- draincloud-db-data:/
volumes:
draincloud-db-data: {}

View File

@ -1,11 +1,22 @@
package app package app
import ( import (
"crypto/rand"
"encoding/base64"
"fmt"
"net/http"
"time"
"git.optclblast.xyz/draincloud/draincloud-light/internal/domain"
"git.optclblast.xyz/draincloud/draincloud-light/internal/logger"
"git.optclblast.xyz/draincloud/draincloud-light/internal/storage"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
) )
type DrainCloud struct { type DrainCloud struct {
mux *gin.Engine mux *gin.Engine
datadase storage.Database
} }
func New() *DrainCloud { func New() *DrainCloud {
@ -16,6 +27,8 @@ func New() *DrainCloud {
authGroup := mux.Group("/auth") authGroup := mux.Group("/auth")
{ {
authGroup.POST("/register", d.Register) authGroup.POST("/register", d.Register)
authGroup.POST("/login", d.Login)
authGroup.POST("/logout", d.Logout)
} }
d.mux = mux d.mux = mux
@ -24,5 +37,92 @@ func New() *DrainCloud {
} }
func (d *DrainCloud) Register(ctx *gin.Context) { func (d *DrainCloud) Register(ctx *gin.Context) {
logger.Debug(ctx, "[register] new request")
req := new(domain.RegisterRequest)
err := ctx.BindJSON(req)
if err != nil {
logger.Error(ctx, "[register] failed to bind request", logger.Err(err))
ctx.JSON(http.StatusBadRequest, map[string]string{
"error": "bad request",
})
return
}
_, err = d.register(ctx, req)
if err != nil {
logger.Error(ctx, "[register] failed to register user", logger.Err(err))
ctx.JSON(http.StatusBadRequest, map[string]string{
"error": err.Error(),
})
return
}
ctx.JSON(http.StatusOK, map[string]bool{
"ok": true,
})
}
type registerResult struct {
cookies []http.Cookie
}
func (d *DrainCloud) register(ctx *gin.Context, req *domain.RegisterRequest) (*registerResult, error) {
if err := validateLoginAndPassword(req.Login, req.Password); err != nil {
return nil, fmt.Errorf("invalid creds: %w", err)
}
_, err := bcrypt.GenerateFromPassword([]byte(req.Password), 10)
if err != nil {
logger.Error(ctx, "[register] failed to generate password", logger.Err(err))
return nil, fmt.Errorf("failed to generate password: %w", err)
}
// _, err = d.datadase.AddUser(ctx, req.Login, req.Login, passwordHash)
// if err != nil {
// return nil, fmt.Errorf("failed to add new user: %w", err)
// }
sessionToken, err := generateSessionToken(50)
if err != nil {
return nil, fmt.Errorf("failed to generate a session token: %w", err)
}
ctx.SetCookie("__Session_token", sessionToken, int((time.Hour * 24).Seconds()), "_path", "_domain", true, true)
csrfToken, err := generateSessionToken(50)
if err != nil {
return nil, fmt.Errorf("failed to generate a csrf token: %w", err)
}
ctx.SetCookie("__Csrf_token", csrfToken, int((time.Hour * 24).Seconds()), "_path", "_domain", true, false)
return &registerResult{}, nil
}
func validateLoginAndPassword(login, password string) error {
if len(login) < 8 {
return fmt.Errorf("login must be longer than 8 chars")
}
if len(password) < 8 {
return fmt.Errorf("password must be longer than 8 chars")
}
return nil
}
func generateSessionToken(length int) (string, error) {
bytes := make([]byte, length)
if _, err := rand.Read(bytes); err != nil {
return "", fmt.Errorf("failed to generate token: %w", err)
}
return base64.URLEncoding.EncodeToString(bytes), nil
}
func (d *DrainCloud) Login(ctx *gin.Context) {
}
func (d *DrainCloud) Logout(ctx *gin.Context) {
} }

View File

@ -4,3 +4,16 @@ type RegisterRequest struct {
Login string `json:"login"` Login string `json:"login"`
Password string `json:"password"` Password string `json:"password"`
} }
type RegisterResponse struct {
Ok bool `json:"ok"`
Message string `json:"message"`
}
type LoginRequest struct {
Login string `json:"login"`
Password string `json:"password"`
}
type LogoutRequest struct {
}

View File

@ -11,7 +11,7 @@ type Database interface {
} }
type AuthStorage interface { type AuthStorage interface {
AddUser(ctx context.Context, user *models.User) error AddUser(ctx context.Context, login string, username string, passwordHash []byte) (uint64, error)
GetUserByLogin(ctx context.Context, login string) (*models.User, error) GetUserByLogin(ctx context.Context, login string) (*models.User, error)
GetUserByID(ctx context.Context, id uint64) (*models.User, error) GetUserByID(ctx context.Context, id uint64) (*models.User, error)
} }

View File

@ -1,53 +0,0 @@
package migrations
import (
"context"
"database/sql"
"git.optclblast.xyz/draincloud/draincloud-light/internal/logger"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigrationContext(upAddUsers, downAddUsers)
}
func upAddUsers(ctx context.Context, tx *sql.Tx) error {
const stmt = `
CREATE TABLE IF NOT EXISTS users (
id bigserial PRIMARY KEY
, username TEXT DEFAULT NULL
, login TEXT NOT NULL UNIQUE
, PASSWORD bytea NOT NULL
, created_at timestamptz DEFAULT current_timestamp
, updated_at timestamptz DEFAULT current_timestamp
);
ALTER TABLE users ADD CONSTRAINT users_username_len CHECK(length(username) > 250) NOT VALID;
ALTER TABLE users ADD CONSTRAINT users_login_len CHECK(length(username) > 250) NOT VALID;
CREATE INDEX CONCURRENTLY idx_users_login ON
users (login);
CREATE INDEX CONCURRENTLY idx_users_username ON
users (username);`
if _, err := tx.ExecContext(ctx, stmt); err != nil {
logger.Error(ctx, "[migration] error", logger.Err(err))
return err
}
return nil
}
func downAddUsers(ctx context.Context, tx *sql.Tx) error {
const stmt = `
DROP INDEX CONCURRENTLY IF EXISTS idx_users_login;
DROP INDEX CONCURRENTLY IF EXISTS idx_users_username;
DROP TABLE IF EXISTS users;`
if _, err := tx.ExecContext(ctx, stmt); err != nil {
logger.Error(ctx, "[migration] error", logger.Err(err))
return err
}
return nil
}

View File

@ -0,0 +1,29 @@
-- +goose Up
-- +goose StatementBegin
SELECT 'up SQL query';
CREATE TABLE IF NOT EXISTS users (
id bigserial PRIMARY KEY
, username TEXT DEFAULT NULL
, login TEXT NOT NULL UNIQUE
, PASSWORD bytea NOT NULL
, created_at timestamptz DEFAULT current_timestamp
, updated_at timestamptz DEFAULT current_timestamp
);
ALTER TABLE users ADD CONSTRAINT users_username_len CHECK(length(username) > 250) NOT VALID;
ALTER TABLE users ADD CONSTRAINT users_login_len CHECK(length(username) > 250) NOT VALID;
CREATE INDEX CONCURRENTLY idx_users_login ON
users (login);
CREATE INDEX CONCURRENTLY idx_users_username ON
users (username);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
SELECT 'down SQL query';
DROP INDEX CONCURRENTLY IF EXISTS idx_users_login;
DROP INDEX CONCURRENTLY IF EXISTS idx_users_username;
DROP TABLE IF EXISTS users;
-- +goose StatementEnd

View File

@ -0,0 +1,26 @@
-- +goose Up
-- +goose StatementBegin
SELECT 'up SQL query';
CREATE TABLE sessions (
id bigserial primary key,
session_token varchar(200) not null unique,
csrf_token varchar(200) not null unique,
user_id bigserial references users(id),
created_at timestamp default current_timestamp,
expired_at timestamp not null
);
create index concurrently if not exists idx_sessions_session_token_csrf_token
on sessions (session_token, csrf_token);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
SELECT 'down SQL query';
drop index concurrently idx_sessions_session_token_csrf_token;
drop table sessions;
-- +goose StatementEnd

View File

@ -0,0 +1,13 @@
-- +goose Up
-- +goose StatementBegin
SELECT 'up SQL query';
create table files_meta (
id bigserial primary key
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
SELECT 'down SQL query';
-- +goose StatementEnd