tmp
This commit is contained in:
parent
cfb4940fe8
commit
c7156f7aab
@ -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: {}
|
@ -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 ®isterResult{}, 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) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
29
migrations/20240930221426_add_users.sql
Normal file
29
migrations/20240930221426_add_users.sql
Normal 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
|
26
migrations/20240930221522_add_sessions.sql
Normal file
26
migrations/20240930221522_add_sessions.sql
Normal 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
|
13
migrations/20240930222057_add_files.sql
Normal file
13
migrations/20240930222057_add_files.sql
Normal 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
|
Loading…
Reference in New Issue
Block a user