Working registration

This commit is contained in:
Lilleman auf Larv 2022-02-24 23:46:24 +01:00
parent a79a4166bd
commit 57ab820693
11 changed files with 299 additions and 36 deletions

View File

@ -1,4 +1,4 @@
ADMIN_API_KEY=changeMe
AUTH_API_URL=http://auth-api:4000
API_URL=http://auth-api:4000
JWT_SHARED_SECRET=changeMe
PORT=4001

1
go.mod
View File

@ -10,6 +10,7 @@ require (
require (
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/klauspost/compress v1.14.3 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.33.0 // indirect

2
go.sum
View File

@ -135,6 +135,8 @@ github.com/gofiber/template v1.6.23 h1:rxIzrukkFrRiC22Z/WRwuySU2z09m932/RkVMAuwc
github.com/gofiber/template v1.6.23/go.mod h1:OpKYcUcfli731QNdeN8Y/EkIdKIzN6zenwOj2JrL/pg=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=

162
src/api/api.go Normal file
View File

@ -0,0 +1,162 @@
package api
import (
"bytes"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"strconv"
"github.com/golang-jwt/jwt"
)
// Method must be a valid REST method, like GET, POST, PUT, DELETE etc
func (api Api) Call(method string, path string, jsonPayload []byte) (ApiRes, error) {
respObj := ApiRes{}
adminToken, _, err := api.getAdminToken()
if err != nil {
return ApiRes{}, err
}
req, err := http.NewRequest(method, api.URL+path, bytes.NewBuffer(jsonPayload))
if err != nil {
api.Log.Error("Could not create request", "method", method, "err", err.Error())
return ApiRes{}, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "bearer "+adminToken)
httpClient := &http.Client{}
res, err := httpClient.Do(req)
if err != nil {
api.Log.Error("Could not call backend API", "err", err.Error())
return ApiRes{}, err
}
defer res.Body.Close()
api.Log.Debug("API res status", "status", res.StatusCode)
respObj.StatusCode = res.StatusCode
if strconv.Itoa(res.StatusCode)[0:1] == "5" {
api.Log.Error("API gave internal server error", "statusCode", res.StatusCode)
return ApiRes{}, errors.New("API gave internal server error")
} else if res.StatusCode == 403 {
api.Log.Error("API responded with 403, Forbidden")
return ApiRes{}, errors.New("API responded with 403, Forbidden")
} else if res.StatusCode == 401 {
api.Log.Error("API responded with 401, Unauthorized")
return ApiRes{}, errors.New("API responded with 403, Unauthorized")
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
api.Log.Error("Could not read res body", "err", err.Error())
return ApiRes{}, err
}
respObj.Body = body
return respObj, nil
}
func (api Api) getAdminToken() (string, AdminClaims, error) {
if api.AdminToken == "" {
// No adminToken exists, obtain it
adminToken, err := api.getAdminTokenCall()
api.AdminToken = adminToken
if err != nil {
api.Log.Error("Could not get admin token")
return "", AdminClaims{}, err
}
parsedToken, err := api.parseAdminToken(api.AdminToken)
if err != nil {
api.Log.Error("Could not parse admin token")
return "", AdminClaims{}, err
}
return api.AdminToken, parsedToken, err
}
parsedToken, err := api.parseAdminToken(api.AdminToken)
if err != nil {
api.Log.Error("Could not parse admin token")
return "", AdminClaims{}, err
}
return api.AdminToken, parsedToken, nil
}
func (api Api) getAdminTokenCall() (string, error) {
req, err := http.NewRequest("POST", api.URL+"/auth/api-key", bytes.NewBufferString("\""+api.AdminApiKey+"\""))
if err != nil {
api.Log.Error("Could not create request", "err", err.Error())
return "", err
}
req.Header.Set("Content-Type", "application/json")
httpClient := &http.Client{}
res, err := httpClient.Do(req)
if err != nil {
api.Log.Error("Could not call backend API", "err", err.Error())
return "", err
}
defer res.Body.Close()
api.Log.Debug("API res status", "status", res.StatusCode)
if strconv.Itoa(res.StatusCode)[0:1] == "5" {
api.Log.Error("API gave internal server error", "statusCode", res.StatusCode)
return "", errors.New("API gave internal server error")
} else if res.StatusCode != 200 {
api.Log.Error("API gave unexpected statusCode", "statusCode", res.StatusCode)
return "", errors.New("API gave unexpected statusCode")
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
api.Log.Error("Could not read res body", "err", err.Error())
return "", err
}
var jsonResp TokenRes
err = json.Unmarshal(body, &jsonResp)
if err != nil {
api.Log.Error("Could not parse JSON from API", "err", err.Error())
return "", err
}
return jsonResp.Jwt, nil
}
func (api Api) parseAdminToken(token string) (AdminClaims, error) {
claims := &AdminClaims{}
parsedToken, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
return []byte(api.JwtSharedSecret), nil
})
if err != nil {
api.Log.Error("Could not parse admin token", "err", err.Error())
return AdminClaims{}, err
}
if !parsedToken.Valid {
err := errors.New("invalid token")
api.Log.Error("Invalid token")
return AdminClaims{}, err
}
claims, ok := parsedToken.Claims.(*AdminClaims)
if !ok {
err := errors.New("parsedToken.Claims.(*AdminClaims) did not return ok")
api.Log.Error(err.Error())
return AdminClaims{}, err
}
return *claims, nil
}

36
src/api/types.go Normal file
View File

@ -0,0 +1,36 @@
package api
import (
"github.com/golang-jwt/jwt"
"go.uber.org/zap"
)
type Api struct {
AdminApiKey string
AdminToken string
JwtSharedSecret string
Log *zap.SugaredLogger
URL string
}
type ApiRes struct {
Body []byte
StatusCode int
}
type ApiResError struct {
Error string `json:"error"`
Field string `json:"field"`
}
type TokenRes struct {
Jwt string `json:"jwt"`
RenewalToken string `json:"renewalToken"`
}
type AdminClaims struct {
AccountID string `json:"accountId"`
AccountFields map[string][]string `json:"accountFields"`
AccountName string `json:"accountName"`
jwt.StandardClaims
}

View File

@ -1,26 +1,25 @@
package handlers
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strconv"
"github.com/gofiber/fiber/v2"
"gitlab.larvit.se/power-plan/auth-ui/src/api"
"gitlab.larvit.se/power-plan/auth-ui/src/views"
)
func (h Handlers) Register(c *fiber.Ctx) error {
c.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
return c.SendString(views.Register())
return c.SendString(views.Register(views.RegisterData{}))
}
type NewUser struct {
Username string
Password string
RepeatPassword string
Name string `json:"name"`
Username string `json:"username"`
Password string `json:"password"`
RepeatPassword string `json:"-"`
}
func (h Handlers) RegisterPost(c *fiber.Ctx) error {
@ -32,8 +31,7 @@ func (h Handlers) RegisterPost(c *fiber.Ctx) error {
h.Log.Debug("Invalid input data", "err", err.Error())
c.Status(400)
}
fmt.Printf("newUser: %v", newUser)
newUser.Name = newUser.Username
jsonPayload, err := json.Marshal(newUser)
if err != nil {
@ -41,30 +39,33 @@ func (h Handlers) RegisterPost(c *fiber.Ctx) error {
return c.Status(500).SendString(views.Error500())
}
req, err := http.NewRequest("POST", "http://localhost:4000/account", bytes.NewBuffer(jsonPayload))
if err != nil {
h.Log.Error("Could not create POST request", "err", err.Error())
return c.Status(500).SendString(views.Error500())
}
req.Header.Set("Content-Type", "application/json")
httpClient := &http.Client{}
res, err := httpClient.Do(req)
apiRes, err := h.Api.Call("POST", "/account", jsonPayload)
if err != nil {
h.Log.Error("Could not call backend API", "err", err.Error())
return c.Status(500).SendString(views.Error500())
}
defer res.Body.Close()
h.Log.Debug("API res status", "status", res.Status)
h.Log.Debug("API res status", "status", apiRes.StatusCode)
body, err := ioutil.ReadAll(res.Body)
if err != nil {
h.Log.Error("Could not read res body", "err", err.Error())
if apiRes.StatusCode == 201 {
return c.SendString(views.Register(views.RegisterData{
OkMsg: "New user created!",
}))
} else if strconv.Itoa(apiRes.StatusCode)[0:1] == "4" {
var jsonResp []api.ApiResError
err := json.Unmarshal(apiRes.Body, &jsonResp)
if err != nil {
h.Log.Error("Could not unmarshal api error response", "err", err.Error())
return c.Status(500).SendString(views.Error500())
}
return c.Status(apiRes.StatusCode).SendString(views.Register(views.RegisterData{
ErrField: jsonResp[0].Field,
ErrMsg: jsonResp[0].Error,
}))
} else {
h.Log.Error("Unexpected API response status", "status", apiRes.StatusCode)
return c.Status(500).SendString(views.Error500())
}
fmt.Println("response Body:", string(body))
return c.SendString(views.Register())
}

View File

@ -2,11 +2,12 @@ package handlers
import (
// jwt "github.com/dgrijalva/jwt-go"
"gitlab.larvit.se/power-plan/auth-ui/src/api"
"go.uber.org/zap"
)
// Handlers is the overall struct for all http request handlers
type Handlers struct {
JwtKey []byte
Log *zap.SugaredLogger
Api api.Api
Log *zap.SugaredLogger
}

View File

@ -6,6 +6,7 @@ import (
"github.com/gofiber/fiber/v2"
"github.com/joho/godotenv"
"gitlab.larvit.se/power-plan/auth-ui/src/api"
h "gitlab.larvit.se/power-plan/auth-ui/src/handlers"
"gitlab.larvit.se/power-plan/auth-ui/src/utils"
)
@ -24,11 +25,23 @@ func main() {
if os.Getenv("ADMIN_API_KEY") == "changeMe" {
log.Error("ADMIN_API_KEY ENV is not set, using very insecure \"changeMe\"")
}
jwtKey := []byte(os.Getenv("JWT_SHARED_SECRET"))
jwtSharedSecret := os.Getenv("JWT_SHARED_SECRET")
adminApiKey := os.Getenv("ADMIN_API_KEY")
apiUrl := os.Getenv("API_URL")
app := fiber.New()
handlers := h.Handlers{JwtKey: jwtKey, Log: log}
apiInstance := api.Api{
AdminApiKey: adminApiKey,
JwtSharedSecret: jwtSharedSecret,
Log: log,
URL: apiUrl,
}
handlers := h.Handlers{
Api: apiInstance,
Log: log,
}
// Log all requests
app.Use(handlers.LogReq)

12
src/view-utils/vutils.go Normal file
View File

@ -0,0 +1,12 @@
package vu // View Utils
// Replacement for ternary. Use like:
// str := "Foo " + Tern(len("") != 0, "bar", "baz") + " yes" // Evaluates to: "Foo baz yes"
// str := "Foo " + Tern(true, "bar", "baz") + " yes" // Evaluates to: "Foo bar yes"
func TernStr(exp bool, expTrue string, expFalse string) string {
if exp {
return expTrue
} else {
return expFalse
}
}

View File

@ -0,0 +1,25 @@
package vu
import "testing"
func TestTern(t *testing.T) {
test1 := "a" + TernStr(1 == 2, "b", "c") + "d"
if test1 != "acd" {
t.Fatalf("Expected \"acd\" but got %v", test1)
}
test2 := "a" + TernStr(1 == 1, "b", "c") + "d"
if test2 != "abd" {
t.Fatalf("Expected \"abd\" but got %v", test1)
}
test3 := "a" + TernStr(false, "b", "c") + "d"
if test3 != "acd" {
t.Fatalf("Expected \"acd\" but got %v", test1)
}
test4 := "a" + TernStr(true, "b", "c") + "d"
if test4 != "abd" {
t.Fatalf("Expected \"abd\" but got %v", test1)
}
}

View File

@ -1,11 +1,21 @@
package views
import "gitlab.larvit.se/power-plan/auth-ui/src/views/layouts"
import (
vu "gitlab.larvit.se/power-plan/auth-ui/src/view-utils"
"gitlab.larvit.se/power-plan/auth-ui/src/views/layouts"
)
func Register() string {
type RegisterData struct {
ErrField string
ErrMsg string
OkMsg string
}
func Register(data RegisterData) string {
content := `
<h1 class="main-title">Register</h1>
<form method="post" class="pure-form pure-form-aligned primary-form">
` + vu.TernStr(len(data.OkMsg) != 0, "<p>"+data.OkMsg+" <a href=\"/\">Login</a></p>", "") + `
<fieldset>
<div class="pure-control-group">
<label for="username">Username</label>
@ -21,7 +31,7 @@ func Register() string {
<label for="repeatPassword">Repeat password</label>
<input type="password" placeholder="Repeat password" name="repeatPassword" id="repeatPassword" />
</div>
` + vu.TernStr(len(data.ErrMsg) != 0, "<p>ERROR! "+data.ErrMsg+"<p>", "") + `
<div class="pure-controls">
<a href="/" class="pure-button">Cancel</a>
<button type="submit" class="pure-button pure-button-primary">Register</button>