Compare commits

..

3 Commits

Author SHA1 Message Date
8261a31679 Basic login. 2023-08-01 23:19:14 +02:00
79af3538bc Fixed generating RSA keys. 2023-08-01 17:12:06 +02:00
22467bc3b4 Started API and loading jwt keys. 2023-07-19 17:29:15 +02:00
15 changed files with 621 additions and 34 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.idea/
data/
secrets/

131
api/auth.go Normal file
View File

@@ -0,0 +1,131 @@
package api
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"github.com/go-chi/jwtauth/v5"
"log"
"net/http"
"os"
"time"
)
var (
jwtAuth *jwtauth.JWTAuth
)
// GetJWTAuth returns the JWT authorizer.
func GetJWTAuth() *jwtauth.JWTAuth {
return jwtAuth
}
// InitJWT initializes JWT authentication.
func InitJWT() {
generateRSAKeys()
jwtAuth = jwtauth.New("RS256", loadRSAPrivateKey(), loadRSAPublicKey())
}
func Login(w http.ResponseWriter, r *http.Request) {
req := struct {
Username string `json:"username"`
Password string `json:"password"`
}{}
if err := decodeJSON(w, r, &req); err != nil {
return
}
// TODO
t, tokenString, _ := jwtAuth.Encode(map[string]interface{}{"username": req.Username})
encodeJSON(w, struct {
AccessToken string `json:"access_token"`
ExpiresAt time.Time `json:"expires_at"`
}{
tokenString,
t.Expiration(),
})
}
func generateRSAKeys() {
err := os.Mkdir("secrets", 0755)
if os.IsExist(err) {
return
} else if err != nil {
log.Fatalf("Error creating secrets directory: %v", err)
}
key, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
log.Fatalf("Error generating RSA key: %v", err)
}
pub := key.Public()
keyPEM := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
},
)
pubPEM := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: x509.MarshalPKCS1PublicKey(pub.(*rsa.PublicKey)),
},
)
if err := os.WriteFile("secrets/jwt.rsa", keyPEM, 0700); err != nil {
log.Fatalf("Error writing private RSA key: %v", err)
}
if err := os.WriteFile("secrets/jwt.rsa.pub", pubPEM, 0755); err != nil {
log.Fatalf("Error writing public RSA key: %v", err)
}
}
func loadRSAPublicKey() *rsa.PublicKey {
data, err := os.ReadFile("secrets/jwt.rsa.pub")
if err != nil {
log.Fatalf("Error loading RSA key: %v", err)
}
block, _ := pem.Decode(data)
if block == nil {
log.Fatalf("Error decoding RSA key: %v", err)
}
key, err := x509.ParsePKCS1PublicKey(block.Bytes)
if err != nil {
log.Fatalf("Error parsing RSA key: %v", err)
}
return key
}
func loadRSAPrivateKey() *rsa.PrivateKey {
data, err := os.ReadFile("secrets/jwt.rsa")
if err != nil {
log.Fatalf("Error loading RSA key: %v", err)
}
block, _ := pem.Decode(data)
if block == nil {
log.Fatalf("Error decoding RSA key: %v", err)
}
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
log.Fatalf("Error parsing RSA key: %v", err)
}
return key
}

33
api/debug.go Normal file
View File

@@ -0,0 +1,33 @@
package api
import (
"encoding/json"
"github.com/go-chi/jwtauth/v5"
"log"
"net/http"
)
func Debug(w http.ResponseWriter, r *http.Request) {
_, claims, err := jwtauth.FromContext(r.Context())
if err != nil {
log.Printf("Error reading token: %v", err)
w.WriteHeader(http.StatusBadRequest)
return
}
data, err := json.Marshal(struct {
Claims map[string]any `json:"claims"`
}{
claims,
})
if err != nil {
log.Printf("Error marshalling response: %v", err)
w.WriteHeader(http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(data)
}

42
api/util.go Normal file
View File

@@ -0,0 +1,42 @@
package api
import (
"encoding/json"
"io"
"log"
"net/http"
"syscall"
)
type apiError struct {
Validation map[string]string `json:"validation"`
Err []error `json:"error"`
}
func decodeJSON(w http.ResponseWriter, r *http.Request, req any) error {
body, err := io.ReadAll(r.Body)
if err != nil {
return err
}
if err := json.Unmarshal(body, &req); err != nil {
return err
}
return nil
}
func encodeJSON(w http.ResponseWriter, resp any) {
respBody, err := json.Marshal(resp)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("Error marshalling response: %v", err)
return
}
if _, err := w.Write(respBody); err != nil && err != syscall.EPIPE {
log.Printf("Error sending response: %v", err)
}
}

View File

@@ -8,7 +8,10 @@
"name": "migo",
"version": "0.0.0",
"dependencies": {
"axios": "^1.4.0",
"js-cookie": "^3.0.5",
"pinia": "^2.1.3",
"qs": "^6.11.2",
"sass": "^1.63.6",
"vue": "^3.3.4",
"vue-router": "^4.2.2"
@@ -1103,6 +1106,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/available-typed-arrays": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
@@ -1115,6 +1123,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/axios": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -1160,7 +1178,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
@@ -1249,6 +1266,17 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -1331,6 +1359,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -1817,6 +1853,25 @@
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
"dev": true
},
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/for-each": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
@@ -1826,6 +1881,19 @@
"is-callable": "^1.1.3"
}
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -1848,8 +1916,7 @@
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/function.prototype.name": {
"version": "1.1.5",
@@ -1882,7 +1949,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
@@ -2019,7 +2085,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1"
},
@@ -2061,7 +2126,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
@@ -2073,7 +2137,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
@@ -2442,6 +2505,14 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
"node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
"engines": {
"node": ">=14"
}
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@@ -2581,6 +2652,25 @@
"node": ">=8.6"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -2848,7 +2938,6 @@
"version": "1.12.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -3139,6 +3228,11 @@
"node": ">= 0.8.0"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/punycode": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
@@ -3148,6 +3242,20 @@
"node": ">=6"
}
},
"node_modules/qs": {
"version": "6.11.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
"integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==",
"dependencies": {
"side-channel": "^1.0.4"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -3409,7 +3517,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",

View File

@@ -11,7 +11,10 @@
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
"axios": "^1.4.0",
"js-cookie": "^3.0.5",
"pinia": "^2.1.3",
"qs": "^6.11.2",
"sass": "^1.63.6",
"vue": "^3.3.4",
"vue-router": "^4.2.2"

View File

@@ -1,4 +1,51 @@
* {
box-sizing: border-box;
}
html, body {
padding: 0;
margin: 0;
font-family: sans-serif;
font-size: 18px;
}
fieldset {
border-width: 0;
padding: 10px 0;
margin: 0;
}
label {
display: block;
font-size: 12px;
color: #767676;
}
input, textarea {
width: 100%;
border: 1px solid #ed6673;
border-radius: 3px;
padding: 6px;
}
input[type~="submit"], button {
width: auto;
border-width: 0;
background: #ed6673;
color: #fff;
padding: 6px 12px;
cursor: pointer;
}
h1 {
margin: 0 0 20px 0;
font-size: 32px;
}
.login {
max-width: 350px;
margin: 200px auto;
padding: 20px;
border: 1px solid #c5c5c5;
border-radius: 3px;
}

View File

@@ -0,0 +1,8 @@
export interface Validation {
[key: string]: string
}
export interface APIError {
validation: Validation
error: string[]
}

View File

@@ -0,0 +1,85 @@
import {AxiosError, AxiosInstance} from "axios";
import type {APIError} from "@/repositories/APIError";
import axios from "@/repositories/axios";
export class Repository {
protected axios: AxiosInstance = axios;
private controller: Map<string, AbortController> = new Map;
protected async performGet<T>(endpoint: string, params: unknown, defaultValue: T, disableCancellation?: boolean): Promise<T | APIError> {
try {
let controller = null;
if (!disableCancellation) {
controller = this.cancel(endpoint);
}
const resp = await this.axios.get<T>(endpoint, {
params,
signal: controller?.signal
});
return Promise.resolve<T>(resp.data || defaultValue);
} catch (e) {
return this.handleException<T>(e, defaultValue);
} finally {
if (!disableCancellation) {
this.clearCancel(endpoint);
}
}
}
protected async performPost<T>(endpoint: string, data: unknown): Promise<T | APIError> {
try {
const resp = await this.axios.post<T>(endpoint, data);
return Promise.resolve<T>(resp.data);
} catch (e) {
const err = e as AxiosError;
return Promise.reject<APIError>(err);
}
}
protected async performPut<T>(endpoint: string, data: unknown): Promise<T | APIError> {
try {
const resp = await this.axios.put<T>(endpoint, data);
return Promise.resolve<T>(resp.data);
} catch (e) {
const err = e as AxiosError;
return Promise.reject<APIError>(err);
}
}
protected async performDelete(endpoint: string, params: unknown): Promise<APIError | null> {
try {
await this.axios.delete<null>(endpoint, {params});
return Promise.resolve<null>(null);
} catch (e) {
return Promise.reject<APIError>(e);
}
}
protected handleException<T>(e: unknown, defaultValue: T): Promise<T | APIError> {
const err = e as AxiosError;
if (err.code && err.code === "ERR_CANCELED") {
return Promise.resolve<T>(defaultValue);
}
return Promise.reject<APIError>(err);
}
private cancel(name: string): AbortController {
const controller = this.controller.get(name);
if (controller) {
controller.abort();
}
const newController = new AbortController();
this.controller.set(name, newController);
return newController;
}
private clearCancel(name: string) {
this.controller.delete(name);
}
}

View File

@@ -0,0 +1,13 @@
import {Repository} from "@/repositories/Repository";
import type {APIError} from "@/repositories/APIError";
export interface TokenResponse {
access_token: string
expires_at: Date
}
export const UserRepository = new class extends Repository {
async login(username: string, password: string): Promise<TokenResponse | APIError> {
return this.performPost<TokenResponse>("/login", {username, password});
}
}

View File

@@ -0,0 +1,74 @@
import axios, {AxiosResponse} from "axios";
import Cookies from "js-cookie";
import qs from "qs";
import {APIError} from "@/repositories/APIError";
let refresh: Promise<void | AxiosResponse> | undefined;
const instance = axios.create({
baseURL: "/api/v1",
timeout: 10000,
paramsSerializer: {
serialize: params => qs.stringify(params, {arrayFormat: "repeat"})
}
});
instance.interceptors.request.use(config => {
const accessToken = Cookies.get("access_token");
if (accessToken && config && config.headers) {
config.headers.Authorization = `Bearer ${accessToken}`;
}
return Promise.resolve(config);
}, err => {
return Promise.reject(err);
});
instance.interceptors.response.use(resp => {
return resp;
}, err => {
if (err.code && err.code === "ERR_CANCELED") {
return Promise.reject(err);
}
if (err.response.status === 401 && err.request.path !== "/refresh") {
return refreshToken().then(() => {
return instance.request(err.config);
}).catch(e => {
console.error(e);
//removeCookies(); TODO
localStorage.clear();
if (e.request.path !== "/login") {
location.href = "/login";
}
});
}
return Promise.reject(err.response.data as APIError);
});
async function refreshToken() {
if (refresh) {
return refresh;
}
const refresh_token = Cookies.get("refresh_token");
refresh = instance.get("/refresh", {params: {refresh_token}}).then(({data: {access_token}}) => {
const now = new Date();
const expires = Date.UTC(now.getUTCFullYear()+1, now.getUTCMonth(), now.getUTCDate());
Cookies.set("access_token", access_token, {
expires: new Date(expires),
secure: true,
domain: "localhost", //getCookieDomain(), TODO
path: "/",
sameSite: "strict"
});
refresh = undefined;
return Promise.resolve();
});
return refresh;
}
export default instance;

View File

@@ -1,9 +1,44 @@
<template>
<h1>Sign In</h1>
<div class="login">
<h1>Sign In</h1>
<form v-on:submit.prevent="login">
<fieldset>
<label for="username">Username</label>
<input type="text" name="username" id="username" v-model="username" />
</fieldset>
<fieldset>
<label for="password">Password</label>
<input type="password" name="password" id="password" v-model="password" />
</fieldset>
<input type="submit" value="Sign In" />
</form>
</div>
</template>
<script lang="ts">
import {defineComponent} from "vue";
import {defineComponent, ref} from "vue";
import {UserRepository} from "@/repositories/UserRepository";
export default defineComponent({});
export default defineComponent({
setup() {
const username = ref("");
const password = ref("");
async function login() {
try {
const token = await UserRepository.login(username.value, password.value);
console.log(token);
} catch (e) {
// TODO
console.error(e);
}
}
return {
username,
password,
login
};
}
});
</script>

View File

@@ -3,10 +3,12 @@ package main
import (
"context"
"embed"
"github.com/Kugelschieber/migo/api"
"github.com/Kugelschieber/migo/db"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
"github.com/go-chi/jwtauth/v5"
"log"
"net/http"
"os"
@@ -32,7 +34,13 @@ func main() {
}
defer db.Close()
api.InitJWT()
dev := os.Getenv("MIGO_DEV") != ""
if dev {
log.Println("Running in development mode")
}
router := chi.NewRouter()
router.Use(middleware.Recoverer)
router.Use(middleware.Compress(5))
@@ -43,6 +51,14 @@ func main() {
AllowCredentials: true,
MaxAge: 86400,
}))
router.Post("/api/v1/login", api.Login)
router.Group(func(r chi.Router) {
r.Use(jwtauth.Verifier(api.GetJWTAuth()))
r.Use(jwtauth.Authenticator)
r.Route("/api/v1", func(r chi.Router) {
r.Get("/debug", api.Debug)
})
})
router.Handle("/admin", http.RedirectHandler("/admin/", http.StatusFound))
router.Route("/admin/", func(r chi.Router) {
if dev {
@@ -77,7 +93,7 @@ func main() {
})
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
// TODO
w.Write([]byte("<h1>Hello World!</h1>"))
_, _ = w.Write([]byte("<h1>Hello World!</h1>"))
})
server := &http.Server{
Handler: router,

16
go.mod
View File

@@ -2,16 +2,18 @@ module github.com/Kugelschieber/migo
go 1.20
require (
github.com/dgraph-io/badger/v3 v3.2103.5
github.com/go-chi/chi/v5 v5.0.10
github.com/go-chi/cors v1.2.1
)
require (
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/dgraph-io/badger/v3 v3.2103.5 // indirect
github.com/dgraph-io/badger/v4 v4.1.0 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/go-chi/chi/v5 v5.0.10 // indirect
github.com/go-chi/cors v1.2.1 // indirect
github.com/go-chi/jwtauth/v5 v5.1.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
@@ -20,7 +22,6 @@ require (
github.com/golang/protobuf v1.3.1 // indirect
github.com/golang/snappy v0.0.3 // indirect
github.com/google/flatbuffers v1.12.1 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/klauspost/compress v1.12.3 // indirect
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
@@ -30,12 +31,9 @@ require (
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/spf13/cobra v0.0.5 // indirect
github.com/spf13/pflag v1.0.3 // indirect
github.com/stretchr/testify v1.8.4 // indirect
go.opencensus.io v0.22.5 // indirect
golang.org/x/crypto v0.10.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.9.0 // indirect
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb // indirect
google.golang.org/grpc v1.20.1 // indirect
)

18
go.sum
View File

@@ -1,5 +1,6 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
@@ -20,10 +21,9 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etly
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg=
github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw=
github.com/dgraph-io/badger/v4 v4.1.0 h1:E38jc0f+RATYrycSUf9LMv/t47XAy+3CApyYSq4APOQ=
github.com/dgraph-io/badger/v4 v4.1.0/go.mod h1:P50u28d39ibBRmIJuQC/NSdBOg46HnHw7al2SW5QRHg=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -53,7 +53,6 @@ github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -87,24 +86,23 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
@@ -140,8 +138,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -164,7 +161,6 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
@@ -194,15 +190,13 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb h1:i1Ppqkc3WQXikh8bXiwHqAN5Rv3/qDCcRk0/Otx73BY=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=