Skip to content

Commit

Permalink
Merge pull request #8 from IgorPolyakov/main
Browse files Browse the repository at this point in the history
Auth it!
  • Loading branch information
IgorPolyakov committed Apr 26, 2024
2 parents 74ed5c0 + 5b95194 commit 68b3ec5
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 4 deletions.
43 changes: 43 additions & 0 deletions api/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,49 @@ servers:
- url: 'http://localhost:4102'
description: Local server
paths:
/api/users/login:
post:
tags:
- Users
summary: Login user
description: Authenticates a user by user_name and password, starts a new session, and returns a session cookie.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
user_name:
type: string
example: exampleUser
password:
type: string
format: password
example: examplePass
responses:
200:
description: User logged in successfully. A session cookie is set.
headers:
Set-Cookie:
description: Session cookie which needs to be included in subsequent requests.
schema:
type: string
example: 'session_id=abc123; Path=/; Max-Age=345600; HttpOnly'
content:
application/json:
schema:
type: object
properties:
data:
type: string
example: 'User logged in'
400:
description: Invalid request body
401:
description: Invalid user_name or password
500:
description: Internal Server Error
/api/users:
get:
tags:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ require (
github.com/gorilla/mux v1.8.1
github.com/ilyakaznacheev/cleanenv v1.5.0
github.com/lib/pq v1.2.0
golang.org/x/crypto v0.22.0
)

require (
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/joho/godotenv v1.5.1 // indirect
golang.org/x/crypto v0.22.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect
)
66 changes: 66 additions & 0 deletions internal/app/api/session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package api

import (
"ctf01d/internal/app/repository"
api_helpers "ctf01d/internal/app/utils"
"database/sql"
"encoding/json"
"net/http"
)

type RequestLogin struct {
Username string `json:"user_name"`
Password string `json:"password"`
}

func LoginSessionHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
var req RequestLogin
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
userRepo := repository.NewUserRepository(db)
user, err := userRepo.GetByUserName(r.Context(), req.Username)
if err != nil || !api_helpers.CheckPasswordHash(req.Password, user.PasswordHash) {
api_helpers.RespondWithJSON(w, http.StatusUnauthorized, map[string]string{"error": "Invalid password or user"})
return
}

sessionRepo := repository.NewSessionRepository(db)
sessionId, err := sessionRepo.StoreSessionInDB(r.Context(), user.Id)
if err != nil {
http.Error(w, "Failed to store session in DB", http.StatusInternalServerError)
return
}

http.SetCookie(w, &http.Cookie{
Name: "session_id",
HttpOnly: true,
Value: sessionId,
Path: "/",
MaxAge: 96 * 3600, // fixme, брать из db
})

api_helpers.RespondWithJSON(w, http.StatusOK, map[string]string{"data": "User logged in"})
}

func LogoutSessionHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("session_id")
if err != nil {
http.Error(w, "No session found", http.StatusUnauthorized)
return
}
sessionRepo := repository.NewSessionRepository(db)
err = sessionRepo.DeleteSessionInDB(r.Context(), cookie.Value)
if err != nil {
http.Error(w, "Failed to delete session", http.StatusInternalServerError)
return
}
http.SetCookie(w, &http.Cookie{
Name: "session_id",
Value: "",
Path: "/",
MaxAge: -1, // Удаление куки
})
api_helpers.RespondWithJSON(w, http.StatusOK, map[string]string{"data": "User logout successful"})
}
54 changes: 54 additions & 0 deletions internal/app/repository/session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package repository

import (
"context"
"database/sql"
"fmt"
"time"
)

type SessionRepository interface {
GetSessionFromDB(ctx context.Context, sessionId string) (int, error)
StoreSessionInDB(ctx context.Context, userId int) (string, error)
DeleteSessionInDB(ctx context.Context, cookie string) error
}

type sessionRepo struct {
db *sql.DB
}

func NewSessionRepository(db *sql.DB) SessionRepository {
return &sessionRepo{db: db}
}

func (r *sessionRepo) GetSessionFromDB(ctx context.Context, sessionId string) (int, error) {
var userId int
err := r.db.QueryRowContext(ctx, "SELECT user_id FROM sessions WHERE id = $1 AND expires_at > NOW()", sessionId).Scan(&userId)
return userId, err
}

func (r *sessionRepo) StoreSessionInDB(ctx context.Context, userId int) (string, error) {
var session string
query := `
INSERT INTO sessions (user_id, expires_at)
VALUES ($1, $2)
ON CONFLICT (user_id) DO
UPDATE SET expires_at = EXCLUDED.expires_at
RETURNING id
`
err := r.db.QueryRowContext(ctx, query, userId, time.Now().Add(96*time.Hour)).Scan(&session)
fmt.Println(session)
if err != nil {
return "", err
}
return session, nil
}

func (r *sessionRepo) DeleteSessionInDB(ctx context.Context, sessionId string) error {
query := "DELETE FROM sessions where id = $1"
_, err := r.db.ExecContext(ctx, query, sessionId)
if err != nil {
return err
}
return nil
}
11 changes: 11 additions & 0 deletions internal/app/repository/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type UserRepository interface {
Create(ctx context.Context, user *models.User) error
AddUserToTeams(ctx context.Context, userId int, teamIds []string) error
GetById(ctx context.Context, id string) (*models.User, error)
GetByUserName(ctx context.Context, id string) (*models.User, error)
Update(ctx context.Context, user *models.User) error
Delete(ctx context.Context, id string) error
List(ctx context.Context) ([]*models.User, error)
Expand Down Expand Up @@ -52,6 +53,16 @@ func (r *userRepo) GetById(ctx context.Context, id string) (*models.User, error)
return user, nil
}

func (r *userRepo) GetByUserName(ctx context.Context, name string) (*models.User, error) {
query := `SELECT id, password_hash FROM users WHERE user_name = $1`
user := &models.User{}
err := r.db.QueryRowContext(ctx, query, name).Scan(&user.Id, &user.PasswordHash)
if err != nil {
return nil, err
}
return user, nil
}

func (r *userRepo) Update(ctx context.Context, user *models.User) error {
query := `UPDATE users SET user_name = $1, avatar_url = $2, role = $3 WHERE id = $4`
_, err := r.db.ExecContext(ctx, query, user.Username, user.AvatarUrl, user.Role, user.Id)
Expand Down
3 changes: 3 additions & 0 deletions internal/app/routers/routers.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ var apiRoutes = ApiRoutes{
ApiRoute{"ListUsers", strings.ToUpper("Get"), "/api/users", api.ListUsersHandler},
ApiRoute{"UpdateUser", strings.ToUpper("Put"), "/api/users/{id}", api.UpdateUserHandler},

ApiRoute{"LoginUser", strings.ToUpper("Post"), "/api/users/login", api.LoginSessionHandler},
ApiRoute{"LogoutUser", strings.ToUpper("Post"), "/api/users/logout", api.LogoutSessionHandler},

ApiRoute{"ListUniversities", strings.ToUpper("Get"), "/api/universities", api.ListUniversitiesHandler},
}

Expand Down
8 changes: 8 additions & 0 deletions migrations/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ CREATE TABLE users (
role VARCHAR(255) NOT NULL CHECK (role IN ('admin', 'player', 'guest'))
);

-- Сессии пользователей
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE TABLE sessions (
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id INTEGER UNIQUE REFERENCES users(id),
expires_at TIMESTAMP NOT NULL
);

-- Университеты
CREATE TABLE universities (
id SERIAL PRIMARY KEY,
Expand Down
31 changes: 30 additions & 1 deletion web/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,42 @@
text-decoration: underline;
}
</style>
</head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<h3>Welcome to example_go_service2 by ctf01d</h3>
<div class="navigation">
<a href="/games/index.html">List Games</a>
<a href="/users/index.html">List Users</a>
<a href="/teams/index.html">List Teams</a>
</div>
<div class="login-form">
<h4>Login</h4>
<form id="login-form">
<input type="text" id="login-username" placeholder="Username" required>
<input type="password" id="login-password" placeholder="Password" required>
<button type="submit">Login</button>
</form>
</div>
<script>
$('#login-form').on('submit', function (e) {
e.preventDefault();
var user_name = $('#login-username').val();
var password = $('#login-password').val();
$.ajax({
url: '/api/users/login',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({ user_name: user_name, password: password }),
success: function (response, status, xhr) {
alert('Login successful!');
window.location.href = "/";
},
error: function () {
alert('Login failed. Check username and password.');
}
});
});
</script>
</body>
</html>
4 changes: 2 additions & 2 deletions web/templates/users/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ <h2>Users</h2>
</div>
<h2>Add New User</h2>
<form id="add-user-form">
<input type="text" id="username" name="username" placeholder="Username" required>
<input type="text" id="user_name" name="username" placeholder="Username" required>
<select id="team" name="team" multiple>
</select>
<input type="password" id="password" name="password" placeholder="Password" required>
Expand Down Expand Up @@ -137,7 +137,7 @@ <h2>Add New User</h2>
e.preventDefault();
var selectedTeams = $('#team').val();
var data = {
user_name: $('#username').val(),
user_name: $('#user_name').val(),
password: $('#password').val(),
avatar_url: $('#avatar_url').val(),
status: $('#status').val(),
Expand Down

0 comments on commit 68b3ec5

Please sign in to comment.