Loads of updates #1

Merged
lilleman merged 3 commits from tmp into master 2023-05-10 22:44:19 +02:00
21 changed files with 42 additions and 37 deletions
Showing only changes of commit b880f2f48b - Show all commits

View File

@ -32,4 +32,13 @@ Obtain an admin GWT: `curl -d '"api-key-goes-here"' -H "Content-Type: applicatio
Use a bearer token to make a call: `curl -H "Content-Type: application/json" -H "Authorization: bearer your-JWT-token-goes-here" -i http://localhost:4000/accounts/{accountID}` Use a bearer token to make a call: `curl -H "Content-Type: application/json" -H "Authorization: bearer your-JWT-token-goes-here" -i http://localhost:4000/accounts/{accountID}`
Create account: `curl -d '{"name": "Bosse", "password": "Hemligt", "fields": [{ "name":"role", "values":["user"]}]}' -H "Content-Type: application/json" -H "Authorization: bearer your-JWT-token-goes-here" -i http://localhost:4000/account` Create account: `curl -d '{"name": "Bosse", "password": "Hemligt", "fields": [{ "name":"role", "values":["user"]}]}' -H "Content-Type: application/json" -H "Authorization: bearer your-JWT-token-goes-here" -i http://localhost:4000/account`
## Development
For local development, run with .env like: `eval $(cat .env) go run src/main.go`
To regenerate the swagger docs folder:
1. Make sure you have swag installed: https://github.com/swaggo/swag
2. cd src && swag init

1
go.mod
View File

@ -9,7 +9,6 @@ require (
github.com/gofiber/fiber/v2 v2.44.0 github.com/gofiber/fiber/v2 v2.44.0
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/jackc/pgx/v4 v4.18.1 github.com/jackc/pgx/v4 v4.18.1
github.com/joho/godotenv v1.5.1
github.com/swaggo/swag v1.16.1 github.com/swaggo/swag v1.16.1
golang.org/x/crypto v0.8.0 golang.org/x/crypto v0.8.0
) )

2
go.sum
View File

@ -101,8 +101,6 @@ github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0f
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0=
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=

View File

@ -3,7 +3,7 @@ package db
import ( import (
"context" "context"
"gitea.larvit.se/pwrpln/auth-api/pkgs/utils" "gitea.larvit.se/pwrpln/auth-api/src/utils"
) )
// RenewalTokenCreate obtain a new renewal token // RenewalTokenCreate obtain a new renewal token

View File

@ -37,9 +37,10 @@ func (h Handlers) AccountDel(c *fiber.Ctx) error {
err := h.Db.AccountDel(accountID) err := h.Db.AccountDel(accountID)
if err != nil { if err != nil {
if err.Error() == "No account found for given accountID" { if err.Error() == "no account found for given accountID" {
return c.Status(404).JSON([]ResJSONError{{Error: err.Error()}}) return c.Status(404).JSON([]ResJSONError{{Error: err.Error()}})
} else { } else {
h.Log.Error("Database error when trying to remove account", "err", err.Error())
return c.Status(500).JSON([]ResJSONError{{Error: "Database error when trying to remove account"}}) return c.Status(500).JSON([]ResJSONError{{Error: "Database error when trying to remove account"}})
} }
} }

View File

@ -5,7 +5,7 @@ import (
"strings" "strings"
"time" "time"
"gitea.larvit.se/pwrpln/auth-api/pkgs/db" "gitea.larvit.se/pwrpln/auth-api/src/db"
jwt "github.com/dgrijalva/jwt-go" jwt "github.com/dgrijalva/jwt-go"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )

View File

@ -3,8 +3,8 @@ package handlers
import ( import (
"strings" "strings"
"gitea.larvit.se/pwrpln/auth-api/pkgs/db" "gitea.larvit.se/pwrpln/auth-api/src/db"
"gitea.larvit.se/pwrpln/auth-api/pkgs/utils" "gitea.larvit.se/pwrpln/auth-api/src/utils"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/google/uuid" "github.com/google/uuid"
) )

View File

@ -1,7 +1,7 @@
package handlers package handlers
import ( import (
"gitea.larvit.se/pwrpln/auth-api/pkgs/db" "gitea.larvit.se/pwrpln/auth-api/src/db"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/google/uuid" "github.com/google/uuid"
) )

View File

@ -1,7 +1,7 @@
package handlers package handlers
import ( import (
"gitea.larvit.se/pwrpln/auth-api/pkgs/db" "gitea.larvit.se/pwrpln/auth-api/src/db"
"gitea.larvit.se/pwrpln/go_log" "gitea.larvit.se/pwrpln/go_log"
jwt "github.com/dgrijalva/jwt-go" jwt "github.com/dgrijalva/jwt-go"
) )

View File

@ -6,21 +6,20 @@ import (
"strings" "strings"
"time" "time"
"gitea.larvit.se/pwrpln/auth-api/pkgs/db" "gitea.larvit.se/pwrpln/auth-api/src/db"
h "gitea.larvit.se/pwrpln/auth-api/pkgs/handlers" h "gitea.larvit.se/pwrpln/auth-api/src/handlers"
"gitea.larvit.se/pwrpln/go_log" "gitea.larvit.se/pwrpln/go_log"
swagger "github.com/arsmn/fiber-swagger/v2" swagger "github.com/arsmn/fiber-swagger/v2"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/jackc/pgx/v4/pgxpool" "github.com/jackc/pgx/v4/pgxpool"
"github.com/joho/godotenv"
// docs are generated by Swag CLI, you have to import them. // docs are generated by Swag CLI, you have to import them.
_ "gitea.larvit.se/pwrpln/auth-api/docs" _ "gitea.larvit.se/pwrpln/auth-api/src/docs"
) )
// Don't put in utils, because it creates import cycle with db... just left it here for now // Don't put in utils, because it creates import cycle with db... just left it here for now
func createAdminAccount(Db db.Db, log go_log.Log) { func createAdminAccount(Db db.Db, log go_log.Log, ADMIN_API_KEY string) {
adminAccountID, uuidErr := uuid.NewRandom() adminAccountID, uuidErr := uuid.NewRandom()
if uuidErr != nil { if uuidErr != nil {
log.Error("Could not create new Uuid", "err", uuidErr.Error()) log.Error("Could not create new Uuid", "err", uuidErr.Error())
@ -29,7 +28,7 @@ func createAdminAccount(Db db.Db, log go_log.Log) {
_, adminAccountErr := Db.AccountCreate(db.AccountCreateInput{ _, adminAccountErr := Db.AccountCreate(db.AccountCreateInput{
ID: adminAccountID, ID: adminAccountID,
Name: "admin", Name: "admin",
APIKey: os.Getenv("ADMIN_API_KEY"), APIKey: ADMIN_API_KEY,
Password: "", Password: "",
Fields: []db.AccountCreateInputFields{{Name: "role", Values: []string{"admin"}}}, Fields: []db.AccountCreateInputFields{{Name: "role", Values: []string{"admin"}}},
}) })
@ -55,11 +54,6 @@ func createAdminAccount(Db db.Db, log go_log.Log) {
func main() { func main() {
log := go_log.GetLog() log := go_log.GetLog()
err := godotenv.Load()
if err != nil {
log.Warn("Error loading .env file, this could be ok if the env file does not exist", "err", err.Error())
}
if os.Getenv("JWT_SHARED_SECRET") == "changeMe" { if os.Getenv("JWT_SHARED_SECRET") == "changeMe" {
log.Warn("JWT_SHARED_SECRET ENV is not set, using very insecure \"changeMe\"") log.Warn("JWT_SHARED_SECRET ENV is not set, using very insecure \"changeMe\"")
} }
@ -79,13 +73,18 @@ func main() {
log.MinLogLvl = minLogLvl log.MinLogLvl = minLogLvl
} }
} }
ADMIN_API_KEY := os.Getenv("ADMIN_API_KEY")
WEB_BIND_HOST := os.Getenv("WEB_BIND_HOST")
DATABASE_URL := os.Getenv("DATABASE_URL")
jwtKey := []byte(os.Getenv("JWT_SHARED_SECRET")) jwtKey := []byte(os.Getenv("JWT_SHARED_SECRET"))
dbPool, err := pgxpool.Connect(context.Background(), os.Getenv("DATABASE_URL")) dbPool, err := pgxpool.Connect(context.Background(), DATABASE_URL)
for err != nil { for err != nil {
log.Warn("Failed to open connection to PostgreSQL database, retrying in 1 second", "err", err.Error()) log.Warn("Failed to open connection to PostgreSQL database, retrying in 1 second", "err", err.Error())
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
dbPool, err = pgxpool.Connect(context.Background(), os.Getenv("DATABASE_URL")) dbPool, err = pgxpool.Connect(context.Background(), DATABASE_URL)
} }
log.Verbose("Connected to PostgreSQL database") log.Verbose("Connected to PostgreSQL database")
defer dbPool.Close() defer dbPool.Close()
@ -95,7 +94,7 @@ func main() {
Db := db.Db{DbPool: dbPool, Log: log} Db := db.Db{DbPool: dbPool, Log: log}
handlers := h.Handlers{Db: Db, JwtKey: jwtKey, Log: log} handlers := h.Handlers{Db: Db, JwtKey: jwtKey, Log: log}
createAdminAccount(Db, log) createAdminAccount(Db, log, ADMIN_API_KEY)
// Log all requests // Log all requests
app.Use(handlers.LogReq) app.Use(handlers.LogReq)
@ -116,14 +115,13 @@ func main() {
app.Post("/renew-token", handlers.RenewToken) app.Post("/renew-token", handlers.RenewToken)
app.Put("/accounts/:accountID/fields", handlers.AccountUpdateFields) app.Put("/accounts/:accountID/fields", handlers.AccountUpdateFields)
log.Info("Starting web server", "WEB_BIND_HOST", os.Getenv("WEB_BIND_HOST")) log.Info("Starting web server", "WEB_BIND_HOST", WEB_BIND_HOST)
webBindHost := os.Getenv("WEB_BIND_HOST") err = app.Listen(WEB_BIND_HOST)
err = app.Listen(webBindHost)
for err != nil { for err != nil {
log.Warn("Could not start web server", "err", err.Error(), "WEB_BIND_HOST", webBindHost) log.Warn("Could not start web server", "err", err.Error(), "WEB_BIND_HOST", WEB_BIND_HOST)
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
err = app.Listen(webBindHost) err = app.Listen(WEB_BIND_HOST)
} }
log.Info("Web server closed, shutting down") log.Info("Web server closed, shutting down")

View File

@ -1,3 +1,3 @@
ADMIN_API_KEY=hihi ADMIN_API_KEY=hihi
AUTH_URL=http://localhost:4000 AUTH_URL=http://127.0.0.1:4000
JWT_SHARED_SECRET=hihi JWT_SHARED_SECRET=hihi

View File

@ -6,7 +6,7 @@ import setConfig from '../test-helpers/config.js';
test('test-cases/00start.js: Wait for auth API to be ready', async t => { test('test-cases/00start.js: Wait for auth API to be ready', async t => {
setConfig({ printConfig: true }); setConfig({ printConfig: true });
const backendHealthCheck = await got(process.env.AUTH_URL, { retry: 2000 }); const backendHealthCheck = await got(process.env.AUTH_URL, { retry: { limit: 2000 }});
t.equal(backendHealthCheck.statusCode, 200, 'Auth API should answer with status code 200'); t.equal(backendHealthCheck.statusCode, 200, 'Auth API should answer with status code 200');
}); });

View File

@ -54,7 +54,7 @@ test('test-cases/01basic.js: GETting the admin account, with the token we just o
}); });
test('test-cases/01basic.js: Creating a new account', async t => { test('test-cases/01basic.js: Creating a new account', async t => {
const res = await got.post(`${process.env.AUTH_URL}/account`, { const res = await got.post(`${process.env.AUTH_URL}/accounts`, {
headers: { 'Authorization': `bearer ${adminJWTString}`}, headers: { 'Authorization': `bearer ${adminJWTString}`},
json: { json: {
fields: [ fields: [
@ -79,7 +79,7 @@ test('test-cases/01basic.js: Creating a new account', async t => {
t.notEqual(user.apiKey, undefined, 'The new account should have an apiKey'); t.notEqual(user.apiKey, undefined, 'The new account should have an apiKey');
try { try {
await got.post(`${process.env.AUTH_URL}/account`, { await got.post(`${process.env.AUTH_URL}/accounts`, {
headers: { 'Authorization': `bearer ${adminJWTString}`}, headers: { 'Authorization': `bearer ${adminJWTString}`},
json: { json: {
fields: [{name: 'role',values: ['user'],}], fields: [{name: 'role',values: ['user'],}],
@ -187,7 +187,7 @@ test('test-cases/01basic.js: Remove an account', async t => {
await got.delete(`${process.env.AUTH_URL}/accounts/a423e690-74b9-4f37-9976-f5bf75a5ea32`, { await got.delete(`${process.env.AUTH_URL}/accounts/a423e690-74b9-4f37-9976-f5bf75a5ea32`, {
headers: { 'Authorization': `bearer ${adminJWTString}`}, headers: { 'Authorization': `bearer ${adminJWTString}`},
responseType: 'json', responseType: 'json',
retry: 0, retry: { limit: 0 },
}); });
t.fail('Response status for DELETing an account that does not exist should be 404'); t.fail('Response status for DELETing an account that does not exist should be 404');
} catch (err) { } catch (err) {
@ -197,7 +197,7 @@ test('test-cases/01basic.js: Remove an account', async t => {
const delRes = await got.delete(`${process.env.AUTH_URL}/accounts/${user.id}`, { const delRes = await got.delete(`${process.env.AUTH_URL}/accounts/${user.id}`, {
headers: { 'Authorization': `bearer ${adminJWTString}`}, headers: { 'Authorization': `bearer ${adminJWTString}`},
responseType: 'json', responseType: 'json',
retry: 0, retry: { limit: 0 },
}); });
t.equal(delRes.statusCode, 204, 'Response status for DELETE should be 204'); t.equal(delRes.statusCode, 204, 'Response status for DELETE should be 204');
@ -206,7 +206,7 @@ test('test-cases/01basic.js: Remove an account', async t => {
await got(`${process.env.AUTH_URL}/accounts/${user.id}`, { await got(`${process.env.AUTH_URL}/accounts/${user.id}`, {
headers: { 'Authorization': `bearer ${adminJWTString}`}, headers: { 'Authorization': `bearer ${adminJWTString}`},
responseType: 'json', responseType: 'json',
retry: 0, retry: { limit: 0 },
}); });
t.fail('Response status for GETing the account again should be 404'); t.fail('Response status for GETing the account again should be 404');
} catch (err) { } catch (err) {