Browse Source

FIX: Image not being saved on ToDo drag&drop category change; FEATURE: Using emails instead of logins

master
parent
commit
c14ca0f431
  1. 12
      pages/category.html
  2. 24
      pages/login.html
  3. 29
      pages/register.html
  4. 14
      src/db/db.go
  5. 10
      src/db/db_test.go
  6. 26
      src/db/group.go
  7. 36
      src/db/todo.go
  8. 36
      src/db/user.go
  9. 8
      src/server/api_test.go
  10. 48
      src/server/endpoints.go
  11. 48
      src/server/validation.go

12
pages/category.html

@ -111,9 +111,9 @@
<tr draggable="true" id="todo-{{.ID}}" ondragstart="dragStart(event);"> <tr draggable="true" id="todo-{{.ID}}" ondragstart="dragStart(event);">
{{ if not .Image }} {{ if not .Image }}
<!-- Display transparent white pixel --> <!-- Display transparent white pixel -->
<td><img src='data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' width="64px" height="64px"></td> <td><img class="todo-image" src='data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' width="64px" height="64px"></td>
{{ else }} {{ else }}
<td><img src='{{ printf "%s" .Image }}' width="64px" height="64px"></td> <td><img class="todo-image" src='{{ printf "%s" .Image }}' width="64px" height="64px"></td>
{{ end }} {{ end }}
<td class="todo-text text-wrap text-break">{{ .Text }}</td> <td class="todo-text text-wrap text-break">{{ .Text }}</td>
<td class="todo-created">{{ .TimeCreated }}</td> <td class="todo-created">{{ .TimeCreated }}</td>
@ -240,15 +240,15 @@ async function drop(event) {
return; return;
} }
// Add a copy of this ToDo in the corresponding group // Update todo's group ID
let result = await postNewTodo({ let result = await updateTodo(todoId, {
text: draggedTodo.getElementsByClassName("todo-text")[0].innerText, text: draggedTodo.getElementsByClassName("todo-text")[0].innerText,
groupId: Number(targetGroupId), groupId: Number(targetGroupId),
dueUnix: Number(draggedTodo.getElementsByClassName("todo-due-unix")[0].innerText), dueUnix: Number(draggedTodo.getElementsByClassName("todo-due-unix")[0].innerText),
image: Array.from(draggedTodo.getElementsByClassName("todo-image")[0].src, char => char.charCodeAt(0))
}); });
// Delete this ToDo in this group window.location.reload();
await deleteTodoRefresh(todoId);
} }
document.addEventListener('DOMContentLoaded', async function() { document.addEventListener('DOMContentLoaded', async function() {

24
pages/login.html

@ -8,14 +8,14 @@
<h3 class="h3 mb-3 fw-normal">Log in</h3> <h3 class="h3 mb-3 fw-normal">Log in</h3>
<form onsubmit="return false;"> <form onsubmit="return false;">
<div class="mb-3 input-group"> <div class="mb-3 input-group">
<img src="/static/images/universal-access.svg" alt="Login" class="input-group-text"> <img src="/static/images/envelope-at.svg" alt="Email" class="input-group-text">
<input <input
type="text" type="email"
class="form-control" class="form-control"
id="input-login" id="input-email"
aria-describedby="Login" aria-describedby="Email"
aria-label="Login" aria-label="email@example.com"
placeholder="Login" placeholder="email@example.com"
required required
minlength="3"> minlength="3">
</div> </div>
@ -42,21 +42,21 @@
<script> <script>
async function logIn() { async function logIn() {
let loginInput = document.getElementById("input-login"); let emailInput = document.getElementById("input-email");
let login = String(loginInput.value).trim(); if (!emailInput.reportValidity()) {
if (login.length < 3) {
return; return;
} }
let email = String(emailInput.value).trim();
let passwordInput = document.getElementById("input-password"); let passwordInput = document.getElementById("input-password");
let password = String(passwordInput.value); if (!passwordInput.reportValidity()) {
if (password.length < 3) {
return; return;
} }
let password = String(passwordInput.value);
password = sha256(password); password = sha256(password);
// Check if auth info is indeed valid // Check if auth info is indeed valid
let response = await doLogin({login: login, password: password}); let response = await doLogin({email: email, password: password});
if (response.ok) { if (response.ok) {
window.location.replace("/"); window.location.replace("/");
} else { } else {

29
pages/register.html

@ -8,19 +8,6 @@
<img src="/static/images/info-circle.svg" alt="Information"></span> <img src="/static/images/info-circle.svg" alt="Information"></span>
</h3> </h3>
<form onsubmit="return false;"> <form onsubmit="return false;">
<div class="mb-3 input-group">
<img src="/static/images/universal-access.svg" alt="Login" class="input-group-text">
<input
type="text"
class="form-control"
id="input-login"
aria-describedby="Login"
aria-label="Login"
placeholder="Login"
required
minlength="3">
</div>
<div class="mb-3 input-group"> <div class="mb-3 input-group">
<img src="/static/images/envelope-at.svg" alt="Email" class="input-group-text"> <img src="/static/images/envelope-at.svg" alt="Email" class="input-group-text">
<input <input
@ -55,27 +42,21 @@
<script> <script>
async function register() { async function register() {
let loginInput = document.getElementById("input-login");
let login = String(loginInput.value).trim();
if (login.length < 3) {
return;
}
let emailInput = document.getElementById("input-email"); let emailInput = document.getElementById("input-email");
let email = String(emailInput.value).trim(); if (!emailInput.reportValidity()) {
if (email.length < 3) {
return; return;
} }
let email = String(emailInput.value).trim();
let passwordInput = document.getElementById("input-password"); let passwordInput = document.getElementById("input-password");
let password = String(passwordInput.value); if (!passwordInput.reportValidity()) {
if (password.length < 3) {
return; return;
} }
let password = String(passwordInput.value);
let passwordSHA256 = sha256(password); let passwordSHA256 = sha256(password);
let postData = { let postData = {
login: login,
email: email, email: email,
password: passwordSHA256, password: passwordSHA256,
}; };

14
src/db/db.go

@ -33,10 +33,10 @@ type DB struct {
func setUpTables(db *DB) error { func setUpTables(db *DB) error {
// Users // Users
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS users( _, err := db.Exec(`CREATE TABLE IF NOT EXISTS users(
login TEXT PRIMARY KEY UNIQUE, email TEXT PRIMARY KEY UNIQUE,
email TEXT NOT NULL UNIQUE,
password TEXT NOT NULL, password TEXT NOT NULL,
time_created_unix INTEGER)`, time_created_unix INTEGER,
confirmed_email INTEGER)`,
) )
if err != nil { if err != nil {
return err return err
@ -47,9 +47,9 @@ func setUpTables(db *DB) error {
id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
name TEXT, name TEXT,
time_created_unix INTEGER, time_created_unix INTEGER,
owner_login TEXT NOT NULL, owner_email TEXT NOT NULL,
removable INTEGER, removable INTEGER,
FOREIGN KEY(owner_login) REFERENCES users(login))`, FOREIGN KEY(owner_email) REFERENCES users(email))`,
) )
if err != nil { if err != nil {
return err return err
@ -62,12 +62,12 @@ func setUpTables(db *DB) error {
text TEXT NOT NULL, text TEXT NOT NULL,
time_created_unix INTEGER, time_created_unix INTEGER,
due_unix INTEGER, due_unix INTEGER,
owner_login TEXT NOT NULL, owner_email TEXT NOT NULL,
is_done INTEGER, is_done INTEGER,
completion_time_unix INTEGER, completion_time_unix INTEGER,
image BLOB, image BLOB,
FOREIGN KEY(group_id) REFERENCES todo_groups(id), FOREIGN KEY(group_id) REFERENCES todo_groups(id),
FOREIGN KEY(owner_login) REFERENCES users(login))`, FOREIGN KEY(owner_email) REFERENCES users(email))`,
) )
if err != nil { if err != nil {
return err return err

10
src/db/db_test.go

@ -32,7 +32,7 @@ func TestApi(t *testing.T) {
// User // User
user := User{ user := User{
Login: "user1", Email: "user1@mail.ru",
Password: "ruohguoeruoger", Password: "ruohguoeruoger",
TimeCreatedUnix: 12421467, TimeCreatedUnix: 12421467,
} }
@ -42,7 +42,7 @@ func TestApi(t *testing.T) {
t.Fatalf("failed to create user: %s", err) t.Fatalf("failed to create user: %s", err)
} }
dbUser, err := db.GetUser(user.Login) dbUser, err := db.GetUser(user.Email)
if err != nil { if err != nil {
t.Fatalf("failed to retrieve created user: %s", err) t.Fatalf("failed to retrieve created user: %s", err)
} }
@ -55,7 +55,7 @@ func TestApi(t *testing.T) {
group := TodoGroup{ group := TodoGroup{
Name: "group1", Name: "group1",
TimeCreatedUnix: 13524534, TimeCreatedUnix: 13524534,
OwnerLogin: user.Login, OwnerEmail: user.Email,
} }
err = db.CreateTodoGroup(group) err = db.CreateTodoGroup(group)
@ -82,7 +82,7 @@ func TestApi(t *testing.T) {
Text: "Do the dishes", Text: "Do the dishes",
TimeCreatedUnix: dbGroup.TimeCreatedUnix, TimeCreatedUnix: dbGroup.TimeCreatedUnix,
DueUnix: 0, DueUnix: 0,
OwnerLogin: user.Login, OwnerEmail: user.Email,
} }
err = db.CreateTodo(todo) err = db.CreateTodo(todo)
if err != nil { if err != nil {
@ -90,7 +90,7 @@ func TestApi(t *testing.T) {
} }
// Now deletion // Now deletion
err = db.DeleteUserClean(user.Login) err = db.DeleteUserClean(user.Email)
if err != nil { if err != nil {
t.Fatalf("couldn't cleanly delete user with all TODOs: %s", err) t.Fatalf("couldn't cleanly delete user with all TODOs: %s", err)
} }

26
src/db/group.go

@ -20,7 +20,6 @@ package db
import ( import (
"database/sql" "database/sql"
"time"
) )
// Todo group structure // Todo group structure
@ -28,16 +27,16 @@ type TodoGroup struct {
ID uint64 `json:"id"` ID uint64 `json:"id"`
Name string `json:"name"` Name string `json:"name"`
TimeCreatedUnix uint64 `json:"timeCreatedUnix"` TimeCreatedUnix uint64 `json:"timeCreatedUnix"`
OwnerLogin string `json:"ownerLogin"` OwnerEmail string `json:"ownerEmail"`
Removable bool `json:"removable"` Removable bool `json:"removable"`
TimeCreated string TimeCreated string
} }
func NewTodoGroup(name string, timeCreatedUnix uint64, ownerLogin string, removable bool) TodoGroup { func NewTodoGroup(name string, timeCreatedUnix uint64, ownerEmail string, removable bool) TodoGroup {
return TodoGroup{ return TodoGroup{
Name: name, Name: name,
TimeCreatedUnix: timeCreatedUnix, TimeCreatedUnix: timeCreatedUnix,
OwnerLogin: ownerLogin, OwnerEmail: ownerEmail,
Removable: removable, Removable: removable,
} }
} }
@ -45,10 +44,10 @@ func NewTodoGroup(name string, timeCreatedUnix uint64, ownerLogin string, remova
// Creates a new TODO group in the database // Creates a new TODO group in the database
func (db *DB) CreateTodoGroup(group TodoGroup) error { func (db *DB) CreateTodoGroup(group TodoGroup) error {
_, err := db.Exec( _, err := db.Exec(
"INSERT INTO todo_groups(name, time_created_unix, owner_login, removable) VALUES(?, ?, ?, ?)", "INSERT INTO todo_groups(name, time_created_unix, owner_email, removable) VALUES(?, ?, ?, ?)",
group.Name, group.Name,
group.TimeCreatedUnix, group.TimeCreatedUnix,
group.OwnerLogin, group.OwnerEmail,
group.Removable, group.Removable,
) )
@ -61,20 +60,15 @@ func scanTodoGroup(rows *sql.Rows) (*TodoGroup, error) {
&newTodoGroup.ID, &newTodoGroup.ID,
&newTodoGroup.Name, &newTodoGroup.Name,
&newTodoGroup.TimeCreatedUnix, &newTodoGroup.TimeCreatedUnix,
&newTodoGroup.OwnerLogin, &newTodoGroup.OwnerEmail,
&newTodoGroup.Removable, &newTodoGroup.Removable,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Convert to Basic time // Convert to Basic time string
timeCreated := time.Unix(int64(newTodoGroup.TimeCreatedUnix), 0) newTodoGroup.TimeCreated = unixToTimeStr(newTodoGroup.TimeCreatedUnix)
if timeCreated.Year() == 1970 {
newTodoGroup.TimeCreated = "None"
} else {
newTodoGroup.TimeCreated = timeCreated.Format(time.DateOnly)
}
return &newTodoGroup, nil return &newTodoGroup, nil
} }
@ -178,13 +172,13 @@ func (db *DB) UpdateTodoGroup(groupID uint64, updatedGroup TodoGroup) error {
return err return err
} }
func (db *DB) DoesUserOwnGroup(groupId uint64, login string) bool { func (db *DB) DoesUserOwnGroup(groupId uint64, email string) bool {
group, err := db.GetTodoGroup(groupId) group, err := db.GetTodoGroup(groupId)
if err != nil { if err != nil {
return false return false
} }
if group.OwnerLogin != login { if group.OwnerEmail != email {
return false return false
} }

36
src/db/todo.go

@ -30,7 +30,7 @@ type Todo struct {
Text string `json:"text"` Text string `json:"text"`
TimeCreatedUnix uint64 `json:"timeCreatedUnix"` TimeCreatedUnix uint64 `json:"timeCreatedUnix"`
DueUnix uint64 `json:"dueUnix"` DueUnix uint64 `json:"dueUnix"`
OwnerLogin string `json:"ownerLogin"` OwnerEmail string `json:"ownerEmail"`
IsDone bool `json:"isDone"` IsDone bool `json:"isDone"`
CompletionTimeUnix uint64 `json:"completionTimeUnix"` CompletionTimeUnix uint64 `json:"completionTimeUnix"`
Image []byte `json:"image"` Image []byte `json:"image"`
@ -56,7 +56,7 @@ func scanTodo(rows *sql.Rows) (*Todo, error) {
&newTodo.Text, &newTodo.Text,
&newTodo.TimeCreatedUnix, &newTodo.TimeCreatedUnix,
&newTodo.DueUnix, &newTodo.DueUnix,
&newTodo.OwnerLogin, &newTodo.OwnerEmail,
&newTodo.IsDone, &newTodo.IsDone,
&newTodo.CompletionTimeUnix, &newTodo.CompletionTimeUnix,
&newTodo.Image, &newTodo.Image,
@ -117,12 +117,12 @@ func (db *DB) GetTodos() ([]*Todo, error) {
// Creates a new TODO in the database // Creates a new TODO in the database
func (db *DB) CreateTodo(todo Todo) error { func (db *DB) CreateTodo(todo Todo) error {
_, err := db.Exec( _, err := db.Exec(
"INSERT INTO todos(group_id, text, time_created_unix, due_unix, owner_login, is_done, completion_time_unix, image) VALUES(?, ?, ?, ?, ?, ?, ?, ?)", "INSERT INTO todos(group_id, text, time_created_unix, due_unix, owner_email, is_done, completion_time_unix, image) VALUES(?, ?, ?, ?, ?, ?, ?, ?)",
todo.GroupID, todo.GroupID,
todo.Text, todo.Text,
todo.TimeCreatedUnix, todo.TimeCreatedUnix,
todo.DueUnix, todo.DueUnix,
todo.OwnerLogin, todo.OwnerEmail,
todo.IsDone, todo.IsDone,
todo.CompletionTimeUnix, todo.CompletionTimeUnix,
todo.Image, todo.Image,
@ -158,12 +158,12 @@ func (db *DB) UpdateTodo(todoID uint64, updatedTodo Todo) error {
} }
// Searches and retrieves TODO groups created by the user // Searches and retrieves TODO groups created by the user
func (db *DB) GetAllUserTodoGroups(login string) ([]*TodoGroup, error) { func (db *DB) GetAllUserTodoGroups(email string) ([]*TodoGroup, error) {
var todoGroups []*TodoGroup var todoGroups []*TodoGroup
rows, err := db.Query( rows, err := db.Query(
"SELECT * FROM todo_groups WHERE owner_login=?", "SELECT * FROM todo_groups WHERE owner_email=?",
login, email,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -182,12 +182,12 @@ func (db *DB) GetAllUserTodoGroups(login string) ([]*TodoGroup, error) {
} }
// Searches and retrieves TODOs created by the user // Searches and retrieves TODOs created by the user
func (db *DB) GetAllUserTodos(login string) ([]*Todo, error) { func (db *DB) GetAllUserTodos(email string) ([]*Todo, error) {
var todos []*Todo var todos []*Todo
rows, err := db.Query( rows, err := db.Query(
"SELECT * FROM todos WHERE owner_login=?", "SELECT * FROM todos WHERE owner_email=?",
login, email,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -207,32 +207,32 @@ func (db *DB) GetAllUserTodos(login string) ([]*Todo, error) {
} }
// Deletes all information regarding TODOs of specified user // Deletes all information regarding TODOs of specified user
func (db *DB) DeleteAllUserTodos(login string) error { func (db *DB) DeleteAllUserTodos(email string) error {
_, err := db.Exec( _, err := db.Exec(
"DELETE FROM todos WHERE owner_login=?", "DELETE FROM todos WHERE owner_email=?",
login, email,
) )
return err return err
} }
// Deletes all information regarding TODO groups of specified user // Deletes all information regarding TODO groups of specified user
func (db *DB) DeleteAllUserTodoGroups(login string) error { func (db *DB) DeleteAllUserTodoGroups(email string) error {
_, err := db.Exec( _, err := db.Exec(
"DELETE FROM todo_groups WHERE owner_login=?", "DELETE FROM todo_groups WHERE owner_email=?",
login, email,
) )
return err return err
} }
func (db *DB) DoesUserOwnTodo(todoId uint64, login string) bool { func (db *DB) DoesUserOwnTodo(todoId uint64, email string) bool {
todo, err := db.GetTodo(todoId) todo, err := db.GetTodo(todoId)
if err != nil { if err != nil {
return false return false
} }
if todo.OwnerLogin != login { if todo.OwnerEmail != email {
return false return false
} }

36
src/db/user.go

@ -22,16 +22,16 @@ import "database/sql"
// User structure // User structure
type User struct { type User struct {
Login string `json:"login"`
Email string `json:"email"` Email string `json:"email"`
Password string `json:"password"` Password string `json:"password"`
TimeCreatedUnix uint64 `json:"timeCreatedUnix"` TimeCreatedUnix uint64 `json:"timeCreatedUnix"`
ConfirmedEmail bool `json:"confirmedEmail"`
} }
func scanUser(rows *sql.Rows) (*User, error) { func scanUser(rows *sql.Rows) (*User, error) {
rows.Next() rows.Next()
var user User var user User
err := rows.Scan(&user.Login, &user.Email, &user.Password, &user.TimeCreatedUnix) err := rows.Scan(&user.Email, &user.Password, &user.TimeCreatedUnix, &user.ConfirmedEmail)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -39,9 +39,9 @@ func scanUser(rows *sql.Rows) (*User, error) {
return &user, nil return &user, nil
} }
// Searches for user with login and returns it // Searches for user with email and returns it
func (db *DB) GetUser(login string) (*User, error) { func (db *DB) GetUser(email string) (*User, error) {
rows, err := db.Query("SELECT * FROM users WHERE login=?", login) rows, err := db.Query("SELECT * FROM users WHERE email=?", email)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -58,50 +58,52 @@ func (db *DB) GetUser(login string) (*User, error) {
// Creates a new user in the database // Creates a new user in the database
func (db *DB) CreateUser(newUser User) error { func (db *DB) CreateUser(newUser User) error {
_, err := db.Exec( _, err := db.Exec(
"INSERT INTO users(login, email, password, time_created_unix) VALUES(?, ?, ?, ?)", "INSERT INTO users(email, password, time_created_unix, confirmed_email) VALUES(?, ?, ?, ?)",
newUser.Login,
newUser.Email, newUser.Email,
newUser.Password, newUser.Password,
newUser.TimeCreatedUnix, newUser.TimeCreatedUnix,
newUser.ConfirmedEmail,
) )
return err return err
} }
// Deletes user with given login // Deletes user with given email address
func (db *DB) DeleteUser(login string) error { func (db *DB) DeleteUser(email string) error {
_, err := db.Exec( _, err := db.Exec(
"DELETE FROM users WHERE login=?", "DELETE FROM users WHERE email=?",
login, email,
) )
return err return err
} }
// Updades user's email address, password, email confirmation with given email address
func (db *DB) UserUpdate(newUser User) error { func (db *DB) UserUpdate(newUser User) error {
_, err := db.Exec( _, err := db.Exec(
"UPDATE users SET email=? password=? WHERE login=?", "UPDATE users SET email=?, password=?, confirmed_email=? WHERE email=?",
newUser.Email, newUser.Email,
newUser.Password, newUser.Password,
newUser.Login, newUser.ConfirmedEmail,
newUser.Email,
) )
return err return err
} }
// Deletes a user and all his TODOs (with groups) as well // Deletes a user and all his TODOs (with groups) as well
func (db *DB) DeleteUserClean(login string) error { func (db *DB) DeleteUserClean(email string) error {
err := db.DeleteAllUserTodoGroups(login) err := db.DeleteAllUserTodoGroups(email)
if err != nil { if err != nil {
return err return err
} }
err = db.DeleteAllUserTodos(login) err = db.DeleteAllUserTodos(email)
if err != nil { if err != nil {
return err return err
} }
err = db.DeleteUser(login) err = db.DeleteUser(email)
if err != nil { if err != nil {
return err return err
} }

8
src/server/api_test.go

@ -54,7 +54,7 @@ func TestApi(t *testing.T) {
// Create a new user // Create a new user
newUser := db.User{ newUser := db.User{
Login: "user1", Email: "user1",
Password: "ruohguoeruoger", Password: "ruohguoeruoger",
TimeCreatedUnix: 12421467, TimeCreatedUnix: 12421467,
} }
@ -81,7 +81,7 @@ func TestApi(t *testing.T) {
// newGroup := db.TodoGroup{ // newGroup := db.TodoGroup{
// Name: "group1", // Name: "group1",
// TimeCreatedUnix: 13524534, // TimeCreatedUnix: 13524534,
// OwnerUsername: newUser.Login, // OwnerUsername: newUser.Email,
// } // }
// newGroupBytes, err := json.Marshal(&newGroup) // newGroupBytes, err := json.Marshal(&newGroup)
// if err != nil { // if err != nil {
@ -92,7 +92,7 @@ func TestApi(t *testing.T) {
// if err != nil { // if err != nil {
// t.Fatalf("failed to create a new POST request to create a new TODO group: %s", err) // t.Fatalf("failed to create a new POST request to create a new TODO group: %s", err)
// } // }
// req.Header.Add(RequestHeaderAuthKey, fmt.Sprintf("%s%s%s", newUser.Login, RequestHeaderAuthSeparator, newUser.Password)) // req.Header.Add(RequestHeaderAuthKey, fmt.Sprintf("%s%s%s", newUser.Email, RequestHeaderAuthSeparator, newUser.Password))
// req.Header.Add(RequestHeaderEncodedB64, "false") // req.Header.Add(RequestHeaderEncodedB64, "false")
// resp, err = http.DefaultClient.Do(req) // resp, err = http.DefaultClient.Do(req)
@ -116,7 +116,7 @@ func TestApi(t *testing.T) {
Text: "Do the dishes", Text: "Do the dishes",
TimeCreatedUnix: uint64(time.Now().UnixMicro()), TimeCreatedUnix: uint64(time.Now().UnixMicro()),
DueUnix: uint64(time.Now().Add(time.Hour * 5).UnixMicro()), DueUnix: uint64(time.Now().Add(time.Hour * 5).UnixMicro()),
OwnerLogin: newUser.Login, OwnerEmail: newUser.Email,
} }
newTodoBytes, err := json.Marshal(&newTodo) newTodoBytes, err := json.Marshal(&newTodo)

48
src/server/endpoints.go

@ -65,30 +65,30 @@ func (s *Server) EndpointUserCreate(w http.ResponseWriter, req *http.Request) {
// Insert into DB // Insert into DB
err = s.db.CreateUser(user) err = s.db.CreateUser(user)
if err != nil { if err != nil {
logger.Error("[Server][EndpointUserCreate] Failed to insert new user \"%s\" data: %s", user.Login, err) logger.Error("[Server][EndpointUserCreate] Failed to insert new user \"%s\" data: %s", user.Email, err)
http.Error(w, "Failed to create user", http.StatusInternalServerError) http.Error(w, "Failed to create user", http.StatusInternalServerError)
return return
} }
logger.Info("[Server][EndpointUserCreate] Created a new user with login \"%s\"", user.Login) logger.Info("[Server][EndpointUserCreate] Created a new user with email \"%s\"", user.Email)
// Create a non-removable default category // Create a non-removable default category
err = s.db.CreateTodoGroup(db.NewTodoGroup( err = s.db.CreateTodoGroup(db.NewTodoGroup(
"Notes", "Notes",
uint64(time.Now().Unix()), uint64(time.Now().Unix()),
user.Login, user.Email,
false, false,
)) ))
if err != nil { if err != nil {
http.Error(w, "Failed to create default group", http.StatusInternalServerError) http.Error(w, "Failed to create default group", http.StatusInternalServerError)
logger.Error("[Server][EndpojntUserCreate] Failed to create a default group for %s: %s", user.Login, err) logger.Error("[Server][EndpojntUserCreate] Failed to create a default group for %s: %s", user.Email, err)
return return
} }
// Send cookie // Send cookie
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: "auth", Name: "auth",
Value: fmt.Sprintf("%s:%s", user.Login, user.Password), Value: fmt.Sprintf("%s:%s", user.Email, user.Password),
SameSite: http.SameSiteStrictMode, SameSite: http.SameSiteStrictMode,
HttpOnly: false, HttpOnly: false,
Path: "/", Path: "/",
@ -122,7 +122,7 @@ func (s *Server) EndpointUserLogin(w http.ResponseWriter, req *http.Request) {
} }
// Check auth data // Check auth data
userDB, err := s.db.GetUser(user.Login) userDB, err := s.db.GetUser(user.Email)
if err != nil { if err != nil {
logger.Error("[Server][EndpointUserLogin] Failed to fetch user information from DB: %s", err) logger.Error("[Server][EndpointUserLogin] Failed to fetch user information from DB: %s", err)
http.Error(w, "Failed to fetch user information", http.StatusInternalServerError) http.Error(w, "Failed to fetch user information", http.StatusInternalServerError)
@ -137,7 +137,7 @@ func (s *Server) EndpointUserLogin(w http.ResponseWriter, req *http.Request) {
// Send cookie // Send cookie
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: "auth", Name: "auth",
Value: fmt.Sprintf("%s:%s", user.Login, user.Password), Value: fmt.Sprintf("%s:%s", user.Email, user.Password),
SameSite: http.SameSiteStrictMode, SameSite: http.SameSiteStrictMode,
HttpOnly: false, HttpOnly: false,
Path: "/", Path: "/",
@ -177,10 +177,10 @@ func (s *Server) EndpointUserUpdate(w http.ResponseWriter, req *http.Request) {
} }
// Check whether the user in request is the user specified in JSON // Check whether the user in request is the user specified in JSON
login := GetLoginFromReq(req) email := GetLoginFromReq(req)
if login != user.Login { if email != user.Email {
// Gotcha! // Gotcha!
logger.Warning("[Server][EndpointUserUpdate] %s tried to update user information of %s!", login, user.Login) logger.Warning("[Server][EndpointUserUpdate] %s tried to update user information of %s!", email, user.Email)
http.Error(w, "Logins do not match", http.StatusForbidden) http.Error(w, "Logins do not match", http.StatusForbidden)
return return
} }
@ -189,11 +189,11 @@ func (s *Server) EndpointUserUpdate(w http.ResponseWriter, req *http.Request) {
err = s.db.UserUpdate(user) err = s.db.UserUpdate(user)
if err != nil { if err != nil {
http.Error(w, "Failed to update user", http.StatusInternalServerError) http.Error(w, "Failed to update user", http.StatusInternalServerError)
logger.Error("[Server][EndpointUserUpdate] Failed to update \"%s\": %s", user.Login, err) logger.Error("[Server][EndpointUserUpdate] Failed to update \"%s\": %s", user.Email, err)
return return
} }
logger.Info("[Server][EndpointUserUpdate] Updated a user with login \"%s\"", user.Login) logger.Info("[Server][EndpointUserUpdate] Updated a user with email \"%s\"", user.Email)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
@ -212,15 +212,15 @@ func (s *Server) EndpointUserDelete(w http.ResponseWriter, req *http.Request) {
} }
// Delete // Delete
login := GetLoginFromReq(req) email := GetLoginFromReq(req)
err := s.db.DeleteUser(login) err := s.db.DeleteUser(email)
if err != nil { if err != nil {
http.Error(w, "Failed to delete user", http.StatusInternalServerError) http.Error(w, "Failed to delete user", http.StatusInternalServerError)
logger.Error("[Server][EndpointUserDelete] Failed to delete \"%s\": %s", login, err) logger.Error("[Server][EndpointUserDelete] Failed to delete \"%s\": %s", email, err)
return return
} }
logger.Info("[Server][EndpointUserDelete] Deleted a user with login \"%s\"", login) logger.Info("[Server][EndpointUserDelete] Deleted a user with email \"%s\"", email)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
@ -239,17 +239,17 @@ func (s *Server) EndpointUserGet(w http.ResponseWriter, req *http.Request) {
} }
// Get information from the database // Get information from the database
login := GetLoginFromReq(req) email := GetLoginFromReq(req)
userDB, err := s.db.GetUser(login) userDB, err := s.db.GetUser(email)
if err != nil { if err != nil {
logger.Error("[Server][EndpointUserGet] Failed to retrieve information on \"%s\": %s", login, err) logger.Error("[Server][EndpointUserGet] Failed to retrieve information on \"%s\": %s", email, err)
http.Error(w, "Failed to fetch information", http.StatusInternalServerError) http.Error(w, "Failed to fetch information", http.StatusInternalServerError)
return return
} }
userDBBytes, err := json.Marshal(&userDB) userDBBytes, err := json.Marshal(&userDB)
if err != nil { if err != nil {
logger.Error("[Server][EndpointUserGet] Failed to marshal information on \"%s\": %s", login, err) logger.Error("[Server][EndpointUserGet] Failed to marshal information on \"%s\": %s", email, err)
http.Error(w, "Failed to marshal information", http.StatusInternalServerError) http.Error(w, "Failed to marshal information", http.StatusInternalServerError)
return return
} }
@ -446,7 +446,7 @@ func (s *Server) EndpointTodoCreate(w http.ResponseWriter, req *http.Request) {
return return
} }
newTodo.OwnerLogin = GetLoginFromReq(req) newTodo.OwnerEmail = GetLoginFromReq(req)
newTodo.TimeCreatedUnix = uint64(time.Now().Unix()) newTodo.TimeCreatedUnix = uint64(time.Now().Unix())
err = s.db.CreateTodo(newTodo) err = s.db.CreateTodo(newTodo)
if err != nil { if err != nil {
@ -457,7 +457,7 @@ func (s *Server) EndpointTodoCreate(w http.ResponseWriter, req *http.Request) {
// Success! // Success!
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
logger.Info("[Server] Created a new TODO for %s", newTodo.OwnerLogin) logger.Info("[Server] Created a new TODO for %s", newTodo.OwnerEmail)
} }
func (s *Server) EndpointUserTodosGet(w http.ResponseWriter, req *http.Request) { func (s *Server) EndpointUserTodosGet(w http.ResponseWriter, req *http.Request) {
@ -582,7 +582,7 @@ func (s *Server) EndpointTodoGroupCreate(w http.ResponseWriter, req *http.Reques
} }
// Add group to the database // Add group to the database
newGroup.OwnerLogin = GetLoginFromReq(req) newGroup.OwnerEmail = GetLoginFromReq(req)
newGroup.TimeCreatedUnix = uint64(time.Now().Unix()) newGroup.TimeCreatedUnix = uint64(time.Now().Unix())
newGroup.Removable = true newGroup.Removable = true
err = s.db.CreateTodoGroup(newGroup) err = s.db.CreateTodoGroup(newGroup)
@ -593,7 +593,7 @@ func (s *Server) EndpointTodoGroupCreate(w http.ResponseWriter, req *http.Reques
// Success! // Success!
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
logger.Info("[Server] Created a new TODO group for %s", newGroup.OwnerLogin) logger.Info("[Server] Created a new TODO group for %s", newGroup.OwnerEmail)
} }
func (s *Server) EndpointTodoGroupGet(w http.ResponseWriter, req *http.Request) { func (s *Server) EndpointTodoGroupGet(w http.ResponseWriter, req *http.Request) {

48
src/server/validation.go

@ -26,26 +26,20 @@ import (
) )
const ( const (
MinimalLoginLength uint = 3 MinimalEmailLength uint = 3
MinimalPasswordLength uint = 5 MinimalPasswordLength uint = 5
MaxLoginLength uint = 60 MaxEmailLength uint = 60
MaxPasswordLength uint = 250 MaxPasswordLength uint = 250
MaxTodoLength uint = 150 MaxTodoLength uint = 150
) )
// Check if user is valid. Returns false and a reason-string if not // Check if user is valid. Returns false and a reason-string if not
func IsUserValid(user db.User) (bool, string) { func IsUserValid(user db.User) (bool, string) {
if uint(len(user.Login)) < MinimalLoginLength { if uint(len(user.Email)) < MinimalEmailLength {
return false, "Login is too small" return false, "Email is too small"
}
if uint(len(user.Login)) > MaxLoginLength {
return false, fmt.Sprintf("Login is too big; Login should be up to %d characters", MaxLoginLength)
}
for _, char := range user.Login {
if char < 0x21 || char > 0x7E {
// Not printable ASCII char!
return false, "Login has a non printable ASCII character"
} }
if uint(len(user.Email)) > MaxEmailLength {
return false, fmt.Sprintf("Email is too big; Email should be up to %d characters", MaxEmailLength)
} }
if uint(len(user.Password)) < MinimalPasswordLength { if uint(len(user.Password)) < MinimalPasswordLength {
@ -54,19 +48,13 @@ func IsUserValid(user db.User) (bool, string) {
if uint(len(user.Password)) > MaxPasswordLength { if uint(len(user.Password)) > MaxPasswordLength {
return false, fmt.Sprintf("Password is too big; Password should be up to %d characters", MaxPasswordLength) return false, fmt.Sprintf("Password is too big; Password should be up to %d characters", MaxPasswordLength)
} }
for _, char := range user.Password {
if char < 0x21 || char > 0x7E {
// Not printable ASCII char!
return false, "Password has a non printable ASCII character"
}
}
return true, "" return true, ""
} }
// Checks if such a user exists and compares passwords. Returns true if such user exists and passwords do match // Checks if such a user exists and compares passwords. Returns true if such user exists and passwords do match
func IsUserAuthorized(db *db.DB, user db.User) bool { func IsUserAuthorized(db *db.DB, user db.User) bool {
userDB, err := db.GetUser(user.Login) userDB, err := db.GetUser(user.Email)
if err != nil { if err != nil {
return false return false
} }
@ -78,7 +66,7 @@ func IsUserAuthorized(db *db.DB, user db.User) bool {
return true return true
} }
// Returns login and password from a cookie. If an error is encountered, returns empty strings // Returns email and password from a cookie. If an error is encountered, returns empty strings
func AuthFromCookie(cookie *http.Cookie) (string, string) { func AuthFromCookie(cookie *http.Cookie) (string, string) {
if cookie == nil { if cookie == nil {
return "", "" return "", ""
@ -98,35 +86,35 @@ checks if such a user exists and compares passwords.
Returns true if such user exists and passwords do match Returns true if such user exists and passwords do match
*/ */
func IsUserAuthorizedReq(req *http.Request, dbase *db.DB) bool { func IsUserAuthorizedReq(req *http.Request, dbase *db.DB) bool {
var login, password string var email, password string
var ok bool var ok bool
login, password, ok = req.BasicAuth() email, password, ok = req.BasicAuth()
if !ok || login == "" || password == "" { if !ok || email == "" || password == "" {
cookie, err := req.Cookie("auth") cookie, err := req.Cookie("auth")
if err != nil { if err != nil {
return false return false
} }
login, password = AuthFromCookie(cookie) email, password = AuthFromCookie(cookie)
} }
return IsUserAuthorized(dbase, db.User{ return IsUserAuthorized(dbase, db.User{
Login: login, Email: email,
Password: password, Password: password,
}) })
} }
// Returns login value from basic auth or from cookie if the former does not exist // Returns email value from basic auth or from cookie if the former does not exist
func GetLoginFromReq(req *http.Request) string { func GetLoginFromReq(req *http.Request) string {
login, _, ok := req.BasicAuth() email, _, ok := req.BasicAuth()
if !ok || login == "" { if !ok || email == "" {
cookie, err := req.Cookie("auth") cookie, err := req.Cookie("auth")
if err != nil { if err != nil {
return "" return ""
} }
login, _ = AuthFromCookie(cookie) email, _ = AuthFromCookie(cookie)
} }
return login return email
} }

Loading…
Cancel
Save