Loads of updates
This commit is contained in:
@@ -5,7 +5,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// AccountCreate writes a user to database
|
||||
@@ -15,38 +14,31 @@ func (d Db) AccountCreate(input AccountCreateInput) (CreatedAccount, error) {
|
||||
_, err := d.DbPool.Exec(context.Background(), accountSQL, input.ID, input.Name, input.APIKey, input.Password)
|
||||
if err != nil {
|
||||
if strings.HasPrefix(err.Error(), "ERROR: duplicate key") {
|
||||
log.WithFields(log.Fields{"name": input.Name}).Debug("Duplicate name in accounts database")
|
||||
d.Log.Debug("Duplicate name in accounts database", "name", input.Name)
|
||||
} else {
|
||||
log.Error("Database error when trying to add account: " + err.Error())
|
||||
d.Log.Error("Database error when trying to add account", "err", err.Error())
|
||||
}
|
||||
|
||||
return CreatedAccount{}, err
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"id": input.ID,
|
||||
"name": input.Name,
|
||||
}).Info("Added account to database")
|
||||
d.Log.Info("Added account to database", "id", input.ID, "name", input.Name)
|
||||
|
||||
accountFieldsSQL := "INSERT INTO \"accountsFields\" (id, \"accountId\", name, value) VALUES($1,$2,$3,$4);"
|
||||
for _, field := range input.Fields {
|
||||
newFieldID, uuidErr := uuid.NewRandom()
|
||||
if uuidErr != nil {
|
||||
log.Fatal("Could not create new Uuid, err: " + uuidErr.Error())
|
||||
d.Log.Fatal("Could not create new Uuid", "err", uuidErr.Error())
|
||||
}
|
||||
|
||||
_, err := d.DbPool.Exec(context.Background(), accountFieldsSQL, newFieldID, input.ID, field.Name, field.Values)
|
||||
if err != nil {
|
||||
if strings.HasPrefix(err.Error(), "ERROR: duplicate key") {
|
||||
log.Error("Database error when trying to account field: " + err.Error())
|
||||
d.Log.Error("Database error when trying to account field", "err", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"accountId": input.ID,
|
||||
"fieldName": field.Name,
|
||||
"fieldValues": field.Values,
|
||||
}).Debug("Added account field")
|
||||
d.Log.Debug("Added account field", "accountId", input.ID, "fieldName", field.Name, "fieldValues", field.Values)
|
||||
}
|
||||
|
||||
return CreatedAccount{
|
||||
@@ -58,12 +50,7 @@ func (d Db) AccountCreate(input AccountCreateInput) (CreatedAccount, error) {
|
||||
|
||||
// AccountGet fetches an account from the database
|
||||
func (d Db) AccountGet(accountID string, APIKey string, Name string) (Account, error) {
|
||||
logContext := log.WithFields(log.Fields{
|
||||
"accountID": accountID,
|
||||
"APIKey": len(APIKey),
|
||||
})
|
||||
|
||||
logContext.Debug("Trying to get account")
|
||||
d.Log.Debug("Trying to get account", "accountID", accountID, "len(APIKey)", len(APIKey))
|
||||
|
||||
var account Account
|
||||
var searchParam string
|
||||
@@ -82,18 +69,18 @@ func (d Db) AccountGet(accountID string, APIKey string, Name string) (Account, e
|
||||
accountErr := d.DbPool.QueryRow(context.Background(), accountSQL, searchParam).Scan(&account.ID, &account.Created, &account.Name, &account.Password)
|
||||
if accountErr != nil {
|
||||
if accountErr.Error() == "no rows in result set" {
|
||||
logContext.Debug("No account found")
|
||||
d.Log.Debug("No account found", "accountID", accountID, "APIKey", len(APIKey))
|
||||
return Account{}, accountErr
|
||||
}
|
||||
|
||||
logContext.Error("Database error when fetching account, err: " + accountErr.Error())
|
||||
d.Log.Error("Database error when fetching account", "err", accountErr.Error(), "accountID", accountID, "APIKey", len(APIKey))
|
||||
return Account{}, accountErr
|
||||
}
|
||||
|
||||
fieldsSQL := "SELECT name, value FROM \"accountsFields\" WHERE \"accountId\" = $1"
|
||||
rows, fieldsErr := d.DbPool.Query(context.Background(), fieldsSQL, account.ID)
|
||||
if fieldsErr != nil {
|
||||
logContext.Error("Database error when fetching account fields, err: " + accountErr.Error())
|
||||
d.Log.Error("Database error when fetching account fields", "err", accountErr.Error(), "accountID", accountID, "APIKey", len(APIKey))
|
||||
return Account{}, fieldsErr
|
||||
}
|
||||
|
||||
@@ -103,7 +90,7 @@ func (d Db) AccountGet(accountID string, APIKey string, Name string) (Account, e
|
||||
var value []string
|
||||
err := rows.Scan(&name, &value)
|
||||
if err != nil {
|
||||
logContext.Error("Could not get name or value from database row, err: " + err.Error())
|
||||
d.Log.Error("Could not get name or value from database row", "err", err.Error(), "accountID", accountID, "APIKey", len(APIKey))
|
||||
return Account{}, err
|
||||
}
|
||||
account.Fields[name] = value
|
||||
|
||||
@@ -3,22 +3,19 @@ package db
|
||||
import (
|
||||
"context"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gitlab.larvit.se/power-plan/auth/src/utils"
|
||||
)
|
||||
|
||||
// RenewalTokenCreate obtain a new renewal token
|
||||
func (d Db) RenewalTokenCreate(accountID string) (string, error) {
|
||||
logContext := log.WithFields(log.Fields{"accountID": accountID})
|
||||
|
||||
logContext.Debug("Creating new renewal token")
|
||||
d.Log.Debug("Creating new renewal token", "accountID", accountID)
|
||||
|
||||
newToken := utils.RandString(60)
|
||||
|
||||
insertSQL := "INSERT INTO \"renewalTokens\" (\"accountId\",token) VALUES($1,$2);"
|
||||
_, insertErr := d.DbPool.Exec(context.Background(), insertSQL, accountID, newToken)
|
||||
if insertErr != nil {
|
||||
logContext.Error("Could not insert into database table \"renewalTokens\", err: " + insertErr.Error())
|
||||
d.Log.Error("Could not insert into database table \"renewalTokens\"", "err", insertErr.Error(), "accountID", accountID)
|
||||
return "", insertErr
|
||||
}
|
||||
|
||||
@@ -27,7 +24,7 @@ func (d Db) RenewalTokenCreate(accountID string) (string, error) {
|
||||
|
||||
// RenewalTokenGet checks if a valid renewal token exists in database
|
||||
func (d Db) RenewalTokenGet(token string) (string, error) {
|
||||
log.Debug("Trying to get a renewal token")
|
||||
d.Log.Debug("Trying to get a renewal token")
|
||||
|
||||
sql := "SELECT \"accountId\" FROM \"renewalTokens\" WHERE exp >= now() AND token = $1"
|
||||
|
||||
@@ -38,7 +35,7 @@ func (d Db) RenewalTokenGet(token string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
log.Error("Database error when fetching renewal token, err: " + err.Error())
|
||||
d.Log.Error("Database error when fetching renewal token", "err", err.Error())
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -47,12 +44,12 @@ func (d Db) RenewalTokenGet(token string) (string, error) {
|
||||
|
||||
// RenewalTokenRm removes a renewal token from the database
|
||||
func (d Db) RenewalTokenRm(token string) error {
|
||||
log.Debug("Trying to remove a renewal token")
|
||||
d.Log.Debug("Trying to remove a renewal token")
|
||||
|
||||
sql := "DELETE FROM \"renewalTokens\" WHERE token = $1"
|
||||
_, err := d.DbPool.Exec(context.Background(), sql, token)
|
||||
if err != nil {
|
||||
log.Error("Database error when trying to remove token, err: " + err.Error())
|
||||
d.Log.Error("Database error when trying to remove token", "err", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Account is an account as represented in the database
|
||||
@@ -41,4 +42,5 @@ type AccountCreateInput struct {
|
||||
// Db struct
|
||||
type Db struct {
|
||||
DbPool *pgxpool.Pool
|
||||
Log *zap.SugaredLogger
|
||||
}
|
||||
|
||||
366
src/docs/docs.go
Normal file
366
src/docs/docs.go
Normal file
@@ -0,0 +1,366 @@
|
||||
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||
// This file was generated by swaggo/swag
|
||||
|
||||
package docs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/template"
|
||||
"github.com/swaggo/swag"
|
||||
)
|
||||
|
||||
var doc = `{
|
||||
"schemes": {{ marshal .Schemes }},
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "{{.Description}}",
|
||||
"title": "{{.Title}}",
|
||||
"contact": {
|
||||
"name": "Power Plan",
|
||||
"url": "https://http://pwrpln.com/",
|
||||
"email": "lilleman@larvit.se"
|
||||
},
|
||||
"license": {
|
||||
"name": "MIT"
|
||||
},
|
||||
"version": "{{.Version}}"
|
||||
},
|
||||
"host": "{{.Host}}",
|
||||
"basePath": "{{.BasePath}}",
|
||||
"paths": {
|
||||
"/account": {
|
||||
"post": {
|
||||
"description": "Create an account",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"summary": "Create an account",
|
||||
"operationId": "account-create",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/db.CreatedAccount"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"415": {
|
||||
"description": "Unsupported Media Type",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/account/{id}": {
|
||||
"get": {
|
||||
"description": "Get account",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"summary": "Get account",
|
||||
"operationId": "get-account-by-id",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Account ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/db.Account"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"415": {
|
||||
"description": "Unsupported Media Type",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/api-key": {
|
||||
"post": {
|
||||
"description": "Authenticate account by API Key",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"summary": "Authenticate account by API Key",
|
||||
"operationId": "auth-account-by-api-key",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/db.Account"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"415": {
|
||||
"description": "Unsupported Media Type",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/password": {
|
||||
"post": {
|
||||
"description": "Authenticate account by Password",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"summary": "Authenticate account by Password",
|
||||
"operationId": "auth-account-by-password",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/db.Account"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"415": {
|
||||
"description": "Unsupported Media Type",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/renew-token": {
|
||||
"post": {
|
||||
"description": "Renew token",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"summary": "Renew token",
|
||||
"operationId": "renew-token",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/db.Account"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"415": {
|
||||
"description": "Unsupported Media Type",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"db.Account": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created": {
|
||||
"type": "string"
|
||||
},
|
||||
"fields": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"db.CreatedAccount": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"apiKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.ResJSONError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"field": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
type swaggerInfo struct {
|
||||
Version string
|
||||
Host string
|
||||
BasePath string
|
||||
Schemes []string
|
||||
Title string
|
||||
Description string
|
||||
}
|
||||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = swaggerInfo{
|
||||
Version: "0.1",
|
||||
Host: "localhost:3000",
|
||||
BasePath: "/",
|
||||
Schemes: []string{},
|
||||
Title: "JWT Auth API",
|
||||
Description: "This is a tiny http API for auth. Register accounts, auth with api-key or name/password, renew JWT tokens...",
|
||||
}
|
||||
|
||||
type s struct{}
|
||||
|
||||
func (s *s) ReadDoc() string {
|
||||
sInfo := SwaggerInfo
|
||||
sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1)
|
||||
|
||||
t, err := template.New("swagger_info").Funcs(template.FuncMap{
|
||||
"marshal": func(v interface{}) string {
|
||||
a, _ := json.Marshal(v)
|
||||
return string(a)
|
||||
},
|
||||
}).Parse(doc)
|
||||
if err != nil {
|
||||
return doc
|
||||
}
|
||||
|
||||
var tpl bytes.Buffer
|
||||
if err := t.Execute(&tpl, sInfo); err != nil {
|
||||
return doc
|
||||
}
|
||||
|
||||
return tpl.String()
|
||||
}
|
||||
|
||||
func init() {
|
||||
swag.Register(swag.Name, &s{})
|
||||
}
|
||||
304
src/docs/swagger.json
Normal file
304
src/docs/swagger.json
Normal file
@@ -0,0 +1,304 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "This is a tiny http API for auth. Register accounts, auth with api-key or name/password, renew JWT tokens...",
|
||||
"title": "JWT Auth API",
|
||||
"contact": {
|
||||
"name": "Power Plan",
|
||||
"url": "https://http://pwrpln.com/",
|
||||
"email": "lilleman@larvit.se"
|
||||
},
|
||||
"license": {
|
||||
"name": "MIT"
|
||||
},
|
||||
"version": "0.1"
|
||||
},
|
||||
"host": "localhost:3000",
|
||||
"basePath": "/",
|
||||
"paths": {
|
||||
"/account": {
|
||||
"post": {
|
||||
"description": "Create an account",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"summary": "Create an account",
|
||||
"operationId": "account-create",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/db.CreatedAccount"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"415": {
|
||||
"description": "Unsupported Media Type",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/account/{id}": {
|
||||
"get": {
|
||||
"description": "Get account",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"summary": "Get account",
|
||||
"operationId": "get-account-by-id",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Account ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/db.Account"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"415": {
|
||||
"description": "Unsupported Media Type",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/api-key": {
|
||||
"post": {
|
||||
"description": "Authenticate account by API Key",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"summary": "Authenticate account by API Key",
|
||||
"operationId": "auth-account-by-api-key",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/db.Account"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"415": {
|
||||
"description": "Unsupported Media Type",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/password": {
|
||||
"post": {
|
||||
"description": "Authenticate account by Password",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"summary": "Authenticate account by Password",
|
||||
"operationId": "auth-account-by-password",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/db.Account"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"415": {
|
||||
"description": "Unsupported Media Type",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/renew-token": {
|
||||
"post": {
|
||||
"description": "Renew token",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"summary": "Renew token",
|
||||
"operationId": "renew-token",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/db.Account"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"415": {
|
||||
"description": "Unsupported Media Type",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ResJSONError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"db.Account": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created": {
|
||||
"type": "string"
|
||||
},
|
||||
"fields": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"db.CreatedAccount": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"apiKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.ResJSONError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"field": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
203
src/docs/swagger.yaml
Normal file
203
src/docs/swagger.yaml
Normal file
@@ -0,0 +1,203 @@
|
||||
basePath: /
|
||||
definitions:
|
||||
db.Account:
|
||||
properties:
|
||||
created:
|
||||
type: string
|
||||
fields:
|
||||
additionalProperties:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
db.CreatedAccount:
|
||||
properties:
|
||||
apiKey:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
handlers.ResJSONError:
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
field:
|
||||
type: string
|
||||
type: object
|
||||
host: localhost:3000
|
||||
info:
|
||||
contact:
|
||||
email: lilleman@larvit.se
|
||||
name: Power Plan
|
||||
url: https://http://pwrpln.com/
|
||||
description: This is a tiny http API for auth. Register accounts, auth with api-key
|
||||
or name/password, renew JWT tokens...
|
||||
license:
|
||||
name: MIT
|
||||
title: JWT Auth API
|
||||
version: "0.1"
|
||||
paths:
|
||||
/account:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Create an account
|
||||
operationId: account-create
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/db.CreatedAccount'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ResJSONError'
|
||||
"403":
|
||||
description: Forbidden
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ResJSONError'
|
||||
"415":
|
||||
description: Unsupported Media Type
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ResJSONError'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ResJSONError'
|
||||
summary: Create an account
|
||||
/account/{id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get account
|
||||
operationId: get-account-by-id
|
||||
parameters:
|
||||
- description: Account ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/db.Account'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ResJSONError'
|
||||
"403":
|
||||
description: Forbidden
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ResJSONError'
|
||||
"415":
|
||||
description: Unsupported Media Type
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ResJSONError'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ResJSONError'
|
||||
summary: Get account
|
||||
/auth/api-key:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Authenticate account by API Key
|
||||
operationId: auth-account-by-api-key
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/db.Account'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ResJSONError'
|
||||
"403":
|
||||
description: Forbidden
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ResJSONError'
|
||||
"415":
|
||||
description: Unsupported Media Type
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ResJSONError'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ResJSONError'
|
||||
summary: Authenticate account by API Key
|
||||
/auth/password:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Authenticate account by Password
|
||||
operationId: auth-account-by-password
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/db.Account'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ResJSONError'
|
||||
"403":
|
||||
description: Forbidden
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ResJSONError'
|
||||
"415":
|
||||
description: Unsupported Media Type
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ResJSONError'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ResJSONError'
|
||||
summary: Authenticate account by Password
|
||||
/renew-token:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Renew token
|
||||
operationId: renew-token
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/db.Account'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ResJSONError'
|
||||
"403":
|
||||
description: Forbidden
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ResJSONError'
|
||||
"415":
|
||||
description: Unsupported Media Type
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ResJSONError'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ResJSONError'
|
||||
summary: Renew token
|
||||
swagger: "2.0"
|
||||
@@ -4,12 +4,19 @@ import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// Hello handler
|
||||
func (h Handlers) Hello(c *fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
}
|
||||
|
||||
// AccountGet handler
|
||||
// AccountGet godoc
|
||||
// @Summary Get account
|
||||
// @Description Get account
|
||||
// @ID get-account-by-id
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Account ID"
|
||||
// @Success 200 {object} db.Account
|
||||
// @Failure 401 {object} ResJSONError
|
||||
// @Failure 403 {object} ResJSONError
|
||||
// @Failure 415 {object} ResJSONError
|
||||
// @Failure 500 {object} ResJSONError
|
||||
// @Router /account/{id} [get]
|
||||
func (h Handlers) AccountGet(c *fiber.Ctx) error {
|
||||
accountID := c.Params("accountID")
|
||||
// logContext := log.WithFields(log.Fields{"accountID": accountID})
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gitlab.larvit.se/power-plan/auth/src/db"
|
||||
)
|
||||
|
||||
@@ -26,13 +25,13 @@ func (h Handlers) returnTokens(account db.Account, c *fiber.Ctx) error {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := token.SignedString(h.JwtKey)
|
||||
if err != nil {
|
||||
log.Error("Could not create token string, err: " + err.Error())
|
||||
h.Log.Error("Could not create token string", "err", err.Error())
|
||||
return c.Status(500).JSON([]ResJSONError{{Error: "Could not create JWT token string"}})
|
||||
}
|
||||
|
||||
renewalToken, renewalTokenErr := h.Db.RenewalTokenCreate(account.ID.String())
|
||||
if renewalTokenErr != nil {
|
||||
log.Error("Could not create renewal token, err: " + renewalTokenErr.Error())
|
||||
h.Log.Error("Could not create renewal token", "err", renewalTokenErr.Error())
|
||||
return c.Status(500).JSON([]ResJSONError{{Error: "Could not create renewal token"}})
|
||||
}
|
||||
|
||||
@@ -43,15 +42,14 @@ func (h Handlers) returnTokens(account db.Account, c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
func (h Handlers) parseJWT(JWT string) (Claims, error) {
|
||||
logContext := log.WithFields(log.Fields{"JWT": JWT})
|
||||
logContext.Trace("Parsing JWT")
|
||||
h.Log.Debug("Parsing JWT", "JWT", JWT)
|
||||
|
||||
JWT = strings.TrimPrefix(JWT, "bearer ") // Since the Authorization header should always start with "bearer "
|
||||
logContext.WithFields(log.Fields{"TrimmedJWT": JWT}).Trace("JWT trimmed")
|
||||
trimmedJWT := strings.TrimPrefix(JWT, "bearer ") // Since the Authorization header should always start with "bearer "
|
||||
h.Log.Debug("JWT trimmed", "JWT", JWT, "trimmedJWT", trimmedJWT)
|
||||
|
||||
claims := &Claims{}
|
||||
|
||||
token, err := jwt.ParseWithClaims(JWT, claims, func(token *jwt.Token) (interface{}, error) {
|
||||
token, err := jwt.ParseWithClaims(trimmedJWT, claims, func(token *jwt.Token) (interface{}, error) {
|
||||
return h.JwtKey, nil
|
||||
})
|
||||
if err != nil {
|
||||
@@ -75,7 +73,7 @@ func (h Handlers) parseHeaders(c *fiber.Ctx) map[string]string {
|
||||
lineParts := strings.Split(line, ": ")
|
||||
|
||||
if len(lineParts) == 1 {
|
||||
log.WithFields(log.Fields{"line": line}).Trace("Ignoring header line")
|
||||
h.Log.Debug("Ignoring header line", "line", line)
|
||||
} else {
|
||||
headersMap[lineParts[0]] = lineParts[1]
|
||||
}
|
||||
|
||||
@@ -2,15 +2,11 @@ package handlers
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Log all requests
|
||||
func (h Handlers) Log(c *fiber.Ctx) error {
|
||||
log.WithFields(log.Fields{
|
||||
"method": c.Method(),
|
||||
"url": c.OriginalURL(),
|
||||
}).Debug("http request")
|
||||
func (h Handlers) LogReq(c *fiber.Ctx) error {
|
||||
h.Log.Debug("http request", "method", c.Method(), "url", c.OriginalURL())
|
||||
|
||||
c.Next()
|
||||
return nil
|
||||
@@ -22,7 +18,7 @@ func (h Handlers) RequireJSON(c *fiber.Ctx) error {
|
||||
contentType := string(c.Request().Header.ContentType())
|
||||
|
||||
if contentType != "application/json" && contentType != "" {
|
||||
log.WithFields(log.Fields{"content-type": contentType}).Debug("Invalid content-type in request")
|
||||
h.Log.Debug("Invalid content-type in request", "content-type", contentType)
|
||||
return c.Status(415).JSON([]ResJSONError{{Error: "Invalid content-type"}})
|
||||
}
|
||||
|
||||
|
||||
@@ -3,15 +3,24 @@ package handlers
|
||||
import (
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
"gitlab.larvit.se/power-plan/auth/src/db"
|
||||
"gitlab.larvit.se/power-plan/auth/src/utils"
|
||||
)
|
||||
|
||||
// AccountCreate creates a new account
|
||||
// AccountCreate godoc
|
||||
// @Summary Create an account
|
||||
// @Description Create an account
|
||||
// @ID account-create
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} db.CreatedAccount
|
||||
// @Failure 401 {object} ResJSONError
|
||||
// @Failure 403 {object} ResJSONError
|
||||
// @Failure 415 {object} ResJSONError
|
||||
// @Failure 500 {object} ResJSONError
|
||||
// @Router /account [post]
|
||||
func (h Handlers) AccountCreate(c *fiber.Ctx) error {
|
||||
authErr := h.RequireAdminRole(c)
|
||||
if authErr != nil {
|
||||
@@ -44,12 +53,12 @@ func (h Handlers) AccountCreate(c *fiber.Ctx) error {
|
||||
|
||||
newAccountID, uuidErr := uuid.NewRandom()
|
||||
if uuidErr != nil {
|
||||
log.Fatal("Could not create new Uuid, err: " + uuidErr.Error())
|
||||
h.Log.Fatal("Could not create new Uuid", "err", uuidErr.Error())
|
||||
}
|
||||
|
||||
hashedPwd, pwdErr := utils.HashPassword(accountInput.Password)
|
||||
if pwdErr != nil {
|
||||
log.Fatal("Could not hash password, err: " + pwdErr.Error())
|
||||
h.Log.Fatal("Could not hash password", "err", pwdErr.Error())
|
||||
}
|
||||
|
||||
createdAccount, err := h.Db.AccountCreate(db.AccountCreateInput{
|
||||
@@ -70,7 +79,18 @@ func (h Handlers) AccountCreate(c *fiber.Ctx) error {
|
||||
return c.Status(201).JSON(createdAccount)
|
||||
}
|
||||
|
||||
// AccountAuthAPIKey auths an APIKey
|
||||
// AccountAuthAPIKey godoc
|
||||
// @Summary Authenticate account by API Key
|
||||
// @Description Authenticate account by API Key
|
||||
// @ID auth-account-by-api-key
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} db.Account
|
||||
// @Failure 401 {object} ResJSONError
|
||||
// @Failure 403 {object} ResJSONError
|
||||
// @Failure 415 {object} ResJSONError
|
||||
// @Failure 500 {object} ResJSONError
|
||||
// @Router /auth/api-key [post]
|
||||
func (h Handlers) AccountAuthAPIKey(c *fiber.Ctx) error {
|
||||
inputAPIKey := string(c.Request().Body())
|
||||
inputAPIKey = inputAPIKey[1 : len(inputAPIKey)-1]
|
||||
@@ -80,14 +100,25 @@ func (h Handlers) AccountAuthAPIKey(c *fiber.Ctx) error {
|
||||
if accountErr.Error() == "no rows in result set" {
|
||||
return c.Status(403).JSON([]ResJSONError{{Error: "Invalid credentials"}})
|
||||
}
|
||||
log.Error("Something went wrong when trying to fetch account")
|
||||
h.Log.Error("Something went wrong when trying to fetch account", "err", accountErr.Error())
|
||||
return c.Status(500).JSON([]ResJSONError{{Error: "Something went wrong when trying to fetch account"}})
|
||||
}
|
||||
|
||||
return h.returnTokens(resolvedAccount, c)
|
||||
}
|
||||
|
||||
// AccountAuthPassword auths a name/password pair
|
||||
// AccountAuthPassword godoc
|
||||
// @Summary Authenticate account by Password
|
||||
// @Description Authenticate account by Password
|
||||
// @ID auth-account-by-password
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} db.Account
|
||||
// @Failure 401 {object} ResJSONError
|
||||
// @Failure 403 {object} ResJSONError
|
||||
// @Failure 415 {object} ResJSONError
|
||||
// @Failure 500 {object} ResJSONError
|
||||
// @Router /auth/password [post]
|
||||
func (h Handlers) AccountAuthPassword(c *fiber.Ctx) error {
|
||||
type AuthInput struct {
|
||||
Name string `json:"name"`
|
||||
@@ -115,8 +146,19 @@ func (h Handlers) AccountAuthPassword(c *fiber.Ctx) error {
|
||||
return h.returnTokens(resolvedAccount, c)
|
||||
}
|
||||
|
||||
// TokenRenew creates a new renewal token and JWT from an old renewal token
|
||||
func (h Handlers) TokenRenew(c *fiber.Ctx) error {
|
||||
// RenewToken godoc
|
||||
// @Summary Renew token
|
||||
// @Description Renew token
|
||||
// @ID renew-token
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} db.Account
|
||||
// @Failure 401 {object} ResJSONError
|
||||
// @Failure 403 {object} ResJSONError
|
||||
// @Failure 415 {object} ResJSONError
|
||||
// @Failure 500 {object} ResJSONError
|
||||
// @Router /renew-token [post]
|
||||
func (h Handlers) RenewToken(c *fiber.Ctx) error {
|
||||
inputToken := string(c.Request().Body())
|
||||
inputToken = inputToken[1 : len(inputToken)-1]
|
||||
|
||||
@@ -132,13 +174,14 @@ func (h Handlers) TokenRenew(c *fiber.Ctx) error {
|
||||
if accountErr.Error() == "no rows in result set" {
|
||||
return c.Status(500).JSON([]ResJSONError{{Error: "Database missmatch. Token found, but account is missing."}})
|
||||
}
|
||||
log.Error("Something went wrong when trying to fetch account")
|
||||
h.Log.Error("Something went wrong when trying to fetch account", "err", accountErr.Error())
|
||||
return c.Status(500).JSON([]ResJSONError{{Error: "Something went wrong when trying to fetch account"}})
|
||||
}
|
||||
|
||||
rmErr := h.Db.RenewalTokenRm(inputToken)
|
||||
if rmErr != nil {
|
||||
return c.Status(500).JSON([]ResJSONError{{Error: "Could not remove old token, err: " + rmErr.Error()}})
|
||||
h.Log.Error("Something went wrong when trying to fetch account", "err", rmErr.Error())
|
||||
return c.Status(500).JSON([]ResJSONError{{Error: "Could not remove old token"}})
|
||||
}
|
||||
|
||||
return h.returnTokens(resolvedAccount, c)
|
||||
|
||||
@@ -3,6 +3,7 @@ package handlers
|
||||
import (
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
"gitlab.larvit.se/power-plan/auth/src/db"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Claims is the JWT struct
|
||||
@@ -17,6 +18,7 @@ type Claims struct {
|
||||
type Handlers struct {
|
||||
Db db.Db
|
||||
JwtKey []byte
|
||||
Log *zap.SugaredLogger
|
||||
}
|
||||
|
||||
// ResJSONError is an error field that is used in JSON error responses
|
||||
|
||||
55
src/main.go
55
src/main.go
@@ -5,19 +5,25 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
swagger "github.com/arsmn/fiber-swagger/v2"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"github.com/joho/godotenv"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gitlab.larvit.se/power-plan/auth/src/db"
|
||||
h "gitlab.larvit.se/power-plan/auth/src/handlers"
|
||||
"gitlab.larvit.se/power-plan/auth/src/utils"
|
||||
"go.uber.org/zap"
|
||||
|
||||
// docs are generated by Swag CLI, you have to import them.
|
||||
_ "gitlab.larvit.se/power-plan/auth/src/docs"
|
||||
)
|
||||
|
||||
func createAdminAccount(Db db.Db) {
|
||||
// Don't put in utils, because it creates import cycle with db... just left it here for now
|
||||
func createAdminAccount(Db db.Db, log *zap.SugaredLogger) {
|
||||
adminAccountID, uuidErr := uuid.NewRandom()
|
||||
if uuidErr != nil {
|
||||
log.Fatal("Could not create new Uuid, err: " + uuidErr.Error())
|
||||
log.Fatal("Could not create new Uuid", "err", uuidErr.Error())
|
||||
}
|
||||
_, adminAccountErr := Db.AccountCreate(db.AccountCreateInput{
|
||||
ID: adminAccountID,
|
||||
@@ -29,20 +35,30 @@ func createAdminAccount(Db db.Db) {
|
||||
if adminAccountErr != nil && strings.HasPrefix(adminAccountErr.Error(), "ERROR: duplicate key") {
|
||||
log.Info("Admin account already created, nothing written to database")
|
||||
} else if adminAccountErr != nil {
|
||||
log.Fatal("Could not create admin account, err: " + adminAccountErr.Error())
|
||||
log.Fatal("Could not create admin account", "err", adminAccountErr.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// @title JWT Auth API
|
||||
// @version 0.1
|
||||
// @description This is a tiny http API for auth. Register accounts, auth with api-key or name/password, renew JWT tokens...
|
||||
|
||||
// @contact.name Power Plan
|
||||
// @contact.url https://http://pwrpln.com/
|
||||
// @contact.email lilleman@larvit.se
|
||||
|
||||
// @license.name MIT
|
||||
|
||||
// @host localhost:3000
|
||||
// @BasePath /
|
||||
func main() {
|
||||
log := utils.GetLog()
|
||||
|
||||
err := godotenv.Load()
|
||||
if err != nil {
|
||||
log.Warn("Error loading .env file, this could be ok if the env file does not exist")
|
||||
log.Warn("Error loading .env file, this could be ok if the env file does not exist", "err", err.Error())
|
||||
}
|
||||
|
||||
// Add this line for logging filename and line number!
|
||||
// log.SetReportCaller(true)
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
if os.Getenv("JWT_SHARED_SECRET") == "changeMe" {
|
||||
log.Fatal("You must change JWT_SHARED_SECRET in .env")
|
||||
}
|
||||
@@ -53,7 +69,7 @@ func main() {
|
||||
|
||||
dbPool, err := pgxpool.Connect(context.Background(), os.Getenv("DATABASE_URL"))
|
||||
if err != nil {
|
||||
log.Fatal("Failed to open DB connection: ", err)
|
||||
log.Fatal("Failed to open DB connection", "err", err.Error())
|
||||
} else {
|
||||
log.Info("Connected to PostgreSQL database")
|
||||
}
|
||||
@@ -61,28 +77,31 @@ func main() {
|
||||
|
||||
app := fiber.New()
|
||||
|
||||
Db := db.Db{DbPool: dbPool}
|
||||
handlers := h.Handlers{Db: Db, JwtKey: jwtKey}
|
||||
Db := db.Db{DbPool: dbPool, Log: log}
|
||||
handlers := h.Handlers{Db: Db, JwtKey: jwtKey, Log: log}
|
||||
|
||||
createAdminAccount(Db)
|
||||
createAdminAccount(Db, log)
|
||||
|
||||
// Log all requests
|
||||
app.Use(handlers.Log)
|
||||
app.Use(handlers.LogReq)
|
||||
|
||||
// Always require application/json
|
||||
app.Use(handlers.RequireJSON)
|
||||
|
||||
app.Get("/", handlers.Hello)
|
||||
app.Get("/", func(c *fiber.Ctx) error { return c.Redirect("/swagger/index.html") })
|
||||
app.Get("/swagger", func(c *fiber.Ctx) error { return c.Redirect("/swagger/index.html") })
|
||||
app.Get("/swagger/*", swagger.Handler)
|
||||
|
||||
app.Get("/account/:accountID", handlers.AccountGet)
|
||||
app.Post("/account", handlers.AccountCreate)
|
||||
app.Post("/auth/api-key", handlers.AccountAuthAPIKey)
|
||||
app.Post("/auth/password", handlers.AccountAuthPassword)
|
||||
app.Post("/renew-token", handlers.TokenRenew)
|
||||
app.Post("/renew-token", handlers.RenewToken)
|
||||
|
||||
log.WithFields(log.Fields{"WEB_BIND_HOST": os.Getenv("WEB_BIND_HOST")}).Info("Trying to start web server")
|
||||
log.Info("Trying to start web server", "WEB_BIND_HOST", os.Getenv("WEB_BIND_HOST"))
|
||||
|
||||
if err := app.Listen(os.Getenv("WEB_BIND_HOST")); err != nil {
|
||||
log.Fatal(err)
|
||||
log.Fatal("Could not start web server", "err", err.Error())
|
||||
}
|
||||
|
||||
log.Info("Webb server closed, shutting down")
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
@@ -48,3 +51,31 @@ func RandString(n int) string {
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func SyslogTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
|
||||
loc, _ := time.LoadLocation("") // "" == UTC
|
||||
t = t.In(loc)
|
||||
|
||||
enc.AppendString(t.Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
|
||||
func GetLog() *zap.SugaredLogger {
|
||||
cfg := zap.NewProductionConfig()
|
||||
cfg.Development = true
|
||||
cfg.DisableCaller = false
|
||||
cfg.DisableStacktrace = false
|
||||
cfg.Encoding = "console" // "console" or "json"
|
||||
cfg.EncoderConfig.EncodeTime = SyslogTimeEncoder
|
||||
cfg.OutputPaths = []string{"stdout"}
|
||||
cfg.ErrorOutputPaths = []string{"stderr"}
|
||||
cfg.Level.SetLevel(zap.DebugLevel)
|
||||
|
||||
logger, err := cfg.Build()
|
||||
if err != nil {
|
||||
log.Panicf("Could not build logger, err: %v", err)
|
||||
}
|
||||
defer logger.Sync() // flushes buffer, if any
|
||||
log := logger.Sugar()
|
||||
|
||||
return log
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user