diff --git a/api/auth.go b/api/auth.go index f420be6..d70aa81 100644 --- a/api/auth.go +++ b/api/auth.go @@ -3,19 +3,39 @@ package api import ( "encoding/json" "github.com/Sirupsen/logrus" - "github.com/kataras/go-sessions" "github.com/simon987/task_tracker/storage" ) +const MaxUsernameLength = 16 + type LoginRequest struct { - Username []byte - Password []byte + Username string `json:"username"` + Password string `json:"password"` } type LoginResponse struct { - Ok bool - Message string - Manager *storage.Manager + Ok bool `json:"ok"` + Message string `json:"message,omitempty"` + Manager *storage.Manager `json:"manager"` +} + +type RegisterRequest struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type AccountDetails struct { + LoggedIn bool `json:"logged_in"` + Manager *storage.Manager `json:"manager,omitempty"` +} + +func (r *RegisterRequest) isValid() bool { + return len(r.Username) <= MaxUsernameLength +} + +type RegisterResponse struct { + Ok bool `json:"ok"` + Message string `json:"message,omitempty"` } func (api *WebAPI) Login(r *Request) { @@ -31,10 +51,10 @@ func (api *WebAPI) Login(r *Request) { return } - manager, err := api.Database.ValidateCredentials(req.Username, req.Password) + manager, err := api.Database.ValidateCredentials([]byte(req.Username), []byte(req.Password)) if err != nil { logrus.WithError(err).WithFields(logrus.Fields{ - "username": string(manager.Username), + "username": req.Username, }).Warning("Login attempt") r.Json(LoginResponse{ @@ -44,10 +64,88 @@ func (api *WebAPI) Login(r *Request) { return } - sess := sessions.StartFasthttp(r.Ctx) + sess := api.Session.StartFasthttp(r.Ctx) sess.Set("manager", manager) + logrus.Debug("SET") + logrus.Debug(sess.ID()) + logrus.Debug(manager) + + r.OkJson(LoginResponse{ + Manager: manager, + Ok: true, + }) + logrus.WithFields(logrus.Fields{ "username": string(manager.Username), }).Info("Logged in") } + +func (api *WebAPI) Register(r *Request) { + + req := &RegisterRequest{} + err := json.Unmarshal(r.Ctx.Request.Body(), req) + + if err != nil { + r.Json(LoginResponse{ + Ok: false, + Message: "Could not parse request", + }, 400) + return + } + + if !req.isValid() { + r.Json(LoginResponse{ + Ok: false, + Message: "Invalid register request", + }, 400) + return + } + + manager := &storage.Manager{ + Username: string(req.Username), + } + + err = api.Database.SaveManager(manager, []byte(req.Password)) + if err != nil { + logrus.WithError(err).WithFields(logrus.Fields{ + "username": string(manager.Username), + }).Warning("Register attempt") + + r.Json(LoginResponse{ + Ok: false, + Message: err.Error(), + }, 400) + return + } + + sess := api.Session.StartFasthttp(r.Ctx) + sess.Set("manager", manager) + + r.OkJson(RegisterResponse{ + Ok: true, + }) + + logrus.WithFields(logrus.Fields{ + "username": string(manager.Username), + }).Info("Registered") +} + +func (api *WebAPI) AccountDetails(r *Request) { + + sess := api.Session.StartFasthttp(r.Ctx) + manager := sess.Get("manager") + logrus.Debug("GET") + logrus.Debug(sess.ID()) + + if manager == nil { + r.OkJson(AccountDetails{ + LoggedIn: false, + }) + } else { + r.OkJson(AccountDetails{ + LoggedIn: true, + Manager: manager.(*storage.Manager), + }) + } +} diff --git a/api/log.go b/api/log.go index 612158d..5761420 100644 --- a/api/log.go +++ b/api/log.go @@ -39,6 +39,10 @@ func (e *LogRequest) Time() time.Time { func LogRequestMiddleware(h RequestHandler) fasthttp.RequestHandler { return fasthttp.RequestHandler(func(ctx *fasthttp.RequestCtx) { + ctx.Response.Header.Add("Access-Control-Allow-Headers", "Content-Type") + ctx.Response.Header.Add("Access-Control-Allow-Methods", "GET, POST, OPTION") + ctx.Response.Header.Add("Access-Control-Allow-Origin", "*") + logrus.WithFields(logrus.Fields{ "path": string(ctx.Path()), "header": ctx.Request.Header.String(), diff --git a/api/main.go b/api/main.go index da68084..f5bf5fb 100644 --- a/api/main.go +++ b/api/main.go @@ -14,7 +14,8 @@ type WebAPI struct { server *fasthttp.Server router *fasthttprouter.Router Database *storage.Database - SessionConfig *sessions.Config + SessionConfig sessions.Config + Session *sessions.Sessions } type Info struct { @@ -38,11 +39,15 @@ func New() *WebAPI { api.router = &fasthttprouter.Router{} - api.SessionConfig = &sessions.Config{ - Cookie: config.Cfg.SessionCookieName, - Expires: config.Cfg.SessionCookieExpiration, + api.SessionConfig = sessions.Config{ + Cookie: config.Cfg.SessionCookieName, + Expires: config.Cfg.SessionCookieExpiration, + CookieSecureTLS: false, + DisableSubdomainPersistence: false, } + api.Session = sessions.New(api.SessionConfig) + api.server = &fasthttp.Server{ Handler: api.router.Handler, Name: info.Name, @@ -76,6 +81,10 @@ func New() *WebAPI { api.router.POST("/logs", LogRequestMiddleware(api.GetLog)) + api.router.POST("/register", LogRequestMiddleware(api.Register)) + api.router.POST("/login", LogRequestMiddleware(api.Login)) + api.router.GET("/account", LogRequestMiddleware(api.AccountDetails)) + api.router.NotFound = func(ctx *fasthttp.RequestCtx) { if ctx.Request.Header.IsOptions() { diff --git a/config.yml b/config.yml index a32e89a..a5de15b 100755 --- a/config.yml +++ b/config.yml @@ -15,3 +15,7 @@ git: log: # panic, fatal, error, warn, info, debug, trace level: "trace" + +session: + cookie_name: "tt" + expiration: "25m" diff --git a/schema.sql b/schema.sql index 1a7c13c..674ca07 100755 --- a/schema.sql +++ b/schema.sql @@ -76,8 +76,8 @@ CREATE TABLE log_entry CREATE TABLE manager ( id SERIAL PRIMARY KEY, - username TEXT, - password TEXT, + username TEXT UNIQUE, + password BYTEA, website_admin BOOLEAN ); diff --git a/storage/auth.go b/storage/auth.go index efcef74..2613415 100644 --- a/storage/auth.go +++ b/storage/auth.go @@ -62,13 +62,14 @@ func (database *Database) SaveManager(manager *Manager, password []byte) error { hash := crypto.SHA512.New() hash.Write(password) + hash.Write([]byte(manager.Username)) hashedPassword := hash.Sum(nil) row := db.QueryRow(`INSERT INTO manager (username, password, website_admin) VALUES ($1,$2,$3) RETURNING ID`, manager.Username, hashedPassword, manager.WebsiteAdmin) - err := row.Scan(manager.Id) + err := row.Scan(&manager.Id) if err != nil { logrus.WithError(err).WithFields(logrus.Fields{ "username": manager, @@ -100,22 +101,23 @@ func (database *Database) UpdateManager(manager *Manager) { }).Warning("Database.UpdateManager UPDATE") } -func (database *Database) UpdateManagerPassword(id int64, newPassword []byte) { +func (database *Database) UpdateManagerPassword(manager *Manager, newPassword []byte) { hash := crypto.SHA512.New() + hash.Write([]byte(manager.Username)) hash.Write(newPassword) hashedPassword := hash.Sum(nil) db := database.getDB() res, err := db.Exec(`UPDATE manager SET password=$1 WHERE id=$2`, - hashedPassword, id) + hashedPassword, manager.Id) handleErr(err) rowsAffected, _ := res.RowsAffected() logrus.WithError(err).WithFields(logrus.Fields{ "rowsAffected": rowsAffected, - "id": id, + "id": manager.Id, }).Warning("Database.UpdateManagerPassword UPDATE") } diff --git a/storage/project.go b/storage/project.go index 1eb6c98..a8427c7 100644 --- a/storage/project.go +++ b/storage/project.go @@ -132,7 +132,7 @@ func (database *Database) UpdateProject(project *Project) error { } func (database Database) GetAllProjects() *[]Project { - var projects []Project + projects := make([]Project, 0) db := database.getDB() rows, err := db.Query(`SELECT diff --git a/test/config.yml b/test/config.yml index a9a6c48..0324e87 100644 --- a/test/config.yml +++ b/test/config.yml @@ -15,5 +15,5 @@ log: level: "trace" session: - cookie_name: "tt" + cookie_name: "tt_test" expiration: "25m" \ No newline at end of file diff --git a/web/angular/package-lock.json b/web/angular/package-lock.json index db8e5e8..f197231 100644 --- a/web/angular/package-lock.json +++ b/web/angular/package-lock.json @@ -1262,7 +1262,6 @@ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "dev": true, - "optional": true, "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -2516,8 +2515,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true, - "optional": true + "dev": true }, "constants-browserify": { "version": "1.0.0", @@ -2919,8 +2917,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true, - "optional": true + "dev": true }, "depd": { "version": "1.1.2", @@ -3995,8 +3992,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -4017,14 +4013,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4039,20 +4033,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -4169,8 +4160,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -4182,7 +4172,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4197,7 +4186,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4205,14 +4193,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -4231,7 +4217,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -4312,8 +4297,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -4325,7 +4309,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -4411,8 +4394,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -4448,7 +4430,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4468,7 +4449,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4512,14 +4492,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -4528,7 +4506,6 @@ "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", "dev": true, - "optional": true, "requires": { "graceful-fs": "^4.1.2", "inherits": "~2.0.0", @@ -4541,7 +4518,6 @@ "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, - "optional": true, "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -4579,8 +4555,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true, - "optional": true + "dev": true }, "get-stream": { "version": "3.0.0", @@ -4805,8 +4780,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "dev": true, - "optional": true + "dev": true }, "has-value": { "version": "1.0.0", @@ -5562,8 +5536,7 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true, - "optional": true + "dev": true }, "is-windows": { "version": "1.0.2", @@ -6246,7 +6219,6 @@ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, - "optional": true, "requires": { "graceful-fs": "^4.1.2", "parse-json": "^2.2.0", @@ -6259,8 +6231,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "optional": true + "dev": true } } }, @@ -6534,8 +6505,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true, - "optional": true + "dev": true }, "map-visit": { "version": "1.0.0", @@ -7167,7 +7137,6 @@ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, - "optional": true, "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -8259,7 +8228,6 @@ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "dev": true, - "optional": true, "requires": { "load-json-file": "^1.0.0", "normalize-package-data": "^2.3.2", @@ -8271,7 +8239,6 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "dev": true, - "optional": true, "requires": { "graceful-fs": "^4.1.2", "pify": "^2.0.0", @@ -8282,8 +8249,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "optional": true + "dev": true } } }, @@ -8292,7 +8258,6 @@ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "dev": true, - "optional": true, "requires": { "find-up": "^1.0.0", "read-pkg": "^1.0.0" @@ -8303,7 +8268,6 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, - "optional": true, "requires": { "path-exists": "^2.0.0", "pinkie-promise": "^2.0.0" @@ -8314,7 +8278,6 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, - "optional": true, "requires": { "pinkie-promise": "^2.0.0" } @@ -9605,7 +9568,6 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, - "optional": true, "requires": { "is-utf8": "^0.2.0" } @@ -10936,7 +10898,6 @@ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, - "optional": true, "requires": { "string-width": "^1.0.2 || 2" } diff --git a/web/angular/src/app/account-details/account-details.component.css b/web/angular/src/app/account-details/account-details.component.css new file mode 100644 index 0000000..e69de29 diff --git a/web/angular/src/app/account-details/account-details.component.html b/web/angular/src/app/account-details/account-details.component.html new file mode 100644 index 0000000..342f05c --- /dev/null +++ b/web/angular/src/app/account-details/account-details.component.html @@ -0,0 +1,3 @@ +
+ {{authService.account | json}} +diff --git a/web/angular/src/app/account-details/account-details.component.ts b/web/angular/src/app/account-details/account-details.component.ts new file mode 100644 index 0000000..0f9ef4e --- /dev/null +++ b/web/angular/src/app/account-details/account-details.component.ts @@ -0,0 +1,17 @@ +import {Component, OnInit} from '@angular/core'; +import {AuthService} from "../auth.service"; + +@Component({ + selector: 'app-account-details', + templateUrl: './account-details.component.html', + styleUrls: ['./account-details.component.css'] +}) +export class AccountDetailsComponent implements OnInit { + + constructor(private authService: AuthService) { + } + + ngOnInit() { + } + +} diff --git a/web/angular/src/app/api.service.ts b/web/angular/src/app/api.service.ts index 2e805e1..24deacd 100755 --- a/web/angular/src/app/api.service.ts +++ b/web/angular/src/app/api.service.ts @@ -1,11 +1,16 @@ import {Injectable} from '@angular/core'; import {HttpClient} from "@angular/common/http"; import {Project} from "./models/project"; +import {Credentials} from "./models/credentials"; @Injectable() export class ApiService { - private url: string = "http://localhost:42901"; + private url: string = "http://localhost/api"; + private options: { + withCredentials: true, + responseType: "json" + }; constructor( private http: HttpClient, @@ -13,22 +18,36 @@ export class ApiService { } getLogs() { - return this.http.post(this.url + "/logs", "{\"level\":6, \"since\":1}"); + return this.http.post(this.url + "/logs", "{\"level\":6, \"since\":1}", this.options); } getProjects() { - return this.http.get(this.url + "/project/list") + return this.http.get(this.url + "/project/list", this.options) } getProject(id: number) { - return this.http.get(this.url + "/project/get/" + id) + return this.http.get(this.url + "/project/get/" + id, this.options) } createProject(project: Project) { - return this.http.post(this.url + "/project/create", project) + return this.http.post(this.url + "/project/create", project, this.options) } updateProject(project: Project) { - return this.http.post(this.url + "/project/update/" + project.id, project) + return this.http.post(this.url + "/project/update/" + project.id, project, this.options) } + + register(credentials: Credentials) { + return this.http.post(this.url + "/register", credentials, this.options) + } + + login(credentials: Credentials) { + return this.http.post(this.url + "/login", credentials, this.options) + } + + getAccountDetails() { + return this.http.get(this.url + "/account", this.options) + } + + } diff --git a/web/angular/src/app/app-routing.module.ts b/web/angular/src/app/app-routing.module.ts index 8195dad..7989baa 100755 --- a/web/angular/src/app/app-routing.module.ts +++ b/web/angular/src/app/app-routing.module.ts @@ -9,12 +9,12 @@ import {Title} from "@angular/platform-browser"; import {filter} from "rxjs/operators"; import {TranslateService} from "@ngx-translate/core"; import {LoginComponent} from "./login/login.component"; -import {CreateAccountComponent} from "./create-account/create-account.component"; +import {AccountDetailsComponent} from "./account-details/account-details.component"; const routes: Routes = [ {path: "log", component: LogsComponent}, {path: "login", component: LoginComponent}, - {path: "new_account", component: CreateAccountComponent}, + {path: "account", component: AccountDetailsComponent}, {path: "projects", component: ProjectListComponent}, {path: "project/:id", component: ProjectDashboardComponent}, {path: "project/:id/update", component: UpdateProjectComponent}, diff --git a/web/angular/src/app/app.component.html b/web/angular/src/app/app.component.html index 5130957..2d6ebad 100755 --- a/web/angular/src/app/app.component.html +++ b/web/angular/src/app/app.component.html @@ -4,6 +4,8 @@ +