mirror of
https://github.com/Threnklyn/wg-ui.git
synced 2026-06-06 22:00:09 +02:00
Implement auth using oauth2_proxy w/headers
This commit is contained in:
@@ -14,7 +14,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/dgrijalva/jwt-go"
|
|
||||||
"github.com/elazarl/go-bindata-assetfs"
|
"github.com/elazarl/go-bindata-assetfs"
|
||||||
"github.com/google/nftables"
|
"github.com/google/nftables"
|
||||||
"github.com/google/nftables/expr"
|
"github.com/google/nftables/expr"
|
||||||
@@ -30,9 +29,10 @@ import (
|
|||||||
var (
|
var (
|
||||||
dataDir = kingpin.Flag("data-dir", "Directory used for storage").Default("/var/lib/wireguard-ui").String()
|
dataDir = kingpin.Flag("data-dir", "Directory used for storage").Default("/var/lib/wireguard-ui").String()
|
||||||
|
|
||||||
listenAddr = kingpin.Flag("listen-address", "Address to listen to").Default(":8080").String()
|
listenAddr = kingpin.Flag("listen-address", "Address to listen to").Default(":8080").String()
|
||||||
natLink = kingpin.Flag("nat-device", "Network interface to masquerade").Default("wlp2s0").String()
|
natLink = kingpin.Flag("nat-device", "Network interface to masquerade").Default("wlp2s0").String()
|
||||||
clientIPRange = kingpin.Flag("client-ip-range", "Client IP CIDR").Default("172.72.72.1/24").String()
|
clientIPRange = kingpin.Flag("client-ip-range", "Client IP CIDR").Default("172.72.72.1/24").String()
|
||||||
|
authUserHeader = kingpin.Flag("auth-user-header", "Header containing username").Default("X-Forwarded-User").String()
|
||||||
|
|
||||||
wgLinkName = kingpin.Flag("wg-device-name", "Wireguard network device name").Default("wg0").String()
|
wgLinkName = kingpin.Flag("wg-device-name", "Wireguard network device name").Default("wg0").String()
|
||||||
wgListenPort = kingpin.Flag("wg-listen-port", "Wireguard UDP port to listen to").Default("51820").Int()
|
wgListenPort = kingpin.Flag("wg-listen-port", "Wireguard UDP port to listen to").Default("51820").Int()
|
||||||
@@ -278,6 +278,7 @@ func (s *Server) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
router := httprouter.New()
|
router := httprouter.New()
|
||||||
|
router.GET("/api/v1/whoami", s.WhoAmI)
|
||||||
router.GET("/api/v1/users/:user/clients/:client", s.withAuth(s.GetClient))
|
router.GET("/api/v1/users/:user/clients/:client", s.withAuth(s.GetClient))
|
||||||
router.PUT("/api/v1/users/:user/clients/:client", s.withAuth(s.EditClient))
|
router.PUT("/api/v1/users/:user/clients/:client", s.withAuth(s.EditClient))
|
||||||
router.DELETE("/api/v1/users/:user/clients/:client", s.withAuth(s.DeleteClient))
|
router.DELETE("/api/v1/users/:user/clients/:client", s.withAuth(s.DeleteClient))
|
||||||
@@ -307,52 +308,39 @@ func (s *Server) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.WithField("listenAddr", *listenAddr).Info("Starting server")
|
log.WithField("listenAddr", *listenAddr).Info("Starting server")
|
||||||
return http.ListenAndServe(*listenAddr, router)
|
|
||||||
|
return http.ListenAndServe(*listenAddr, s.userFromHeader(router))
|
||||||
}
|
}
|
||||||
|
|
||||||
func userFromJwtToken(r *http.Request) string {
|
func (s *Server) userFromHeader(handler http.Handler) http.Handler {
|
||||||
authHeader := r.Header.Get("authorization")
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if authHeader == "" {
|
user := r.Header.Get(*authUserHeader)
|
||||||
log.Debug("No Authorization header")
|
if user == "" {
|
||||||
return ""
|
log.Debug("Unauthenticated request")
|
||||||
}
|
user = "anonymous"
|
||||||
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(authHeader, "Bearer ") {
|
cookie := http.Cookie{
|
||||||
log.Debug("Incorrect Authorization header: ", authHeader)
|
Name: "wguser",
|
||||||
return ""
|
Value: user,
|
||||||
}
|
Path: "/",
|
||||||
|
}
|
||||||
|
http.SetCookie(w, &cookie)
|
||||||
|
|
||||||
claims := jwt.MapClaims{}
|
ctx := context.WithValue(r.Context(), "user", user)
|
||||||
token, err := jwt.ParseWithClaims(authHeader[7:], &claims, func(token *jwt.Token) (interface{}, error) {
|
handler.ServeHTTP(w, r.WithContext(ctx))
|
||||||
return []byte(""), nil
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if token == nil {
|
|
||||||
log.Debug("Error parsing JWT token: ", err)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
user, ok := claims["email"]
|
|
||||||
if ok {
|
|
||||||
return user.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
user, ok = claims["sub"]
|
|
||||||
if ok {
|
|
||||||
return user.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) withAuth(handler httprouter.Handle) httprouter.Handle {
|
func (s *Server) withAuth(handler httprouter.Handle) httprouter.Handle {
|
||||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
log.Debug("Auth required")
|
log.Debug("Auth required")
|
||||||
|
|
||||||
user := userFromJwtToken(r)
|
user := r.Context().Value("user")
|
||||||
if user == "" {
|
if user == nil {
|
||||||
user = "anonymous"
|
log.Error("Error getting username from request context")
|
||||||
log.Info("Unauthenticated user: ", user)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if user != ps.ByName("user") {
|
if user != ps.ByName("user") {
|
||||||
@@ -361,8 +349,17 @@ func (s *Server) withAuth(handler httprouter.Handle) httprouter.Handle {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.WithValue(r.Context(), "user", user)
|
handler(w, r, ps)
|
||||||
handler(w, r.WithContext(ctx), ps)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) WhoAmI(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
user := r.Context().Value("user").(string)
|
||||||
|
log.Debug(user)
|
||||||
|
err := json.NewEncoder(w).Encode(struct{ User string }{user})
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Generated
+19
@@ -30,6 +30,11 @@
|
|||||||
"integrity": "sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw==",
|
"integrity": "sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/cookie": {
|
||||||
|
"version": "0.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz",
|
||||||
|
"integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow=="
|
||||||
|
},
|
||||||
"@types/estree": {
|
"@types/estree": {
|
||||||
"version": "0.0.39",
|
"version": "0.0.39",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
|
||||||
@@ -380,6 +385,20 @@
|
|||||||
"integrity": "sha512-pMD+MVR538ipqkG5JXeOEbKWS5um1H4LUUccUQG68qpeqBYbzYy79Gh55jkd2TtPdRfUaLWdv6LPP//5Zt0aPQ==",
|
"integrity": "sha512-pMD+MVR538ipqkG5JXeOEbKWS5um1H4LUUccUQG68qpeqBYbzYy79Gh55jkd2TtPdRfUaLWdv6LPP//5Zt0aPQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"cookie": {
|
||||||
|
"version": "0.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
|
||||||
|
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
|
||||||
|
},
|
||||||
|
"cookie-universal": {
|
||||||
|
"version": "2.0.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-universal/-/cookie-universal-2.0.16.tgz",
|
||||||
|
"integrity": "sha512-EHtQ5Tg3UoUHG7LmeV3rlV3iYthkhUuYZ0y86EseypxGcUuvzxuHExEb6mHKDhDPrIrdewAHdG/aCHuG/T4zEg==",
|
||||||
|
"requires": {
|
||||||
|
"@types/cookie": "^0.3.1",
|
||||||
|
"cookie": "^0.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"copy-descriptor": {
|
"copy-descriptor": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"start:dev": "sirv public --dev"
|
"start:dev": "sirv public --dev"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"cookie-universal": "^2.0.16",
|
||||||
"ui": "^0.2.4"
|
"ui": "^0.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -1,10 +1,15 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { onMount } from 'svelte';
|
||||||
import { Router, Link, Route } from "svelte-routing";
|
import { Router, Link, Route } from "svelte-routing";
|
||||||
import About from "./About.svelte";
|
import About from "./About.svelte";
|
||||||
import Clients from "./Clients.svelte";
|
import Clients from "./Clients.svelte";
|
||||||
import EditClient from "./EditClient.svelte";
|
import EditClient from "./EditClient.svelte";
|
||||||
import Nav from "./Nav.svelte";
|
import Nav from "./Nav.svelte";
|
||||||
|
|
||||||
|
import Cookie from "cookie-universal";
|
||||||
|
const cookies = Cookie();
|
||||||
|
export let user = cookies.get("wguser", { fromRes: true});
|
||||||
|
|
||||||
export let url = "";
|
export let url = "";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -14,7 +19,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<Route path="client/:clientId" component="{EditClient}" />
|
<Route path="client/:clientId" component="{EditClient}" />
|
||||||
<Route path="about" component="{About}" />
|
<Route path="about" component="{About}" />
|
||||||
<Route path="/"><Clients /></Route>
|
<Route path="/"><Clients user="{user}" /></Route>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</Router>
|
</Router>
|
||||||
|
|||||||
@@ -11,8 +11,7 @@
|
|||||||
for (var i = 0; i < dev.PrivateKey.length; i++) {
|
for (var i = 0; i < dev.PrivateKey.length; i++) {
|
||||||
hash = dev.PrivateKey.charCodeAt(i) + ((hash << 5) - hash);
|
hash = dev.PrivateKey.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
}
|
}
|
||||||
let color = "hsl(" + (hash % 360) + ",50%,95%)";
|
const color = "hsl(" + (hash % 360) + ",50%,95%)";
|
||||||
console.log("color", color);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -23,7 +22,7 @@
|
|||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body" style="background-color: {color}">
|
<div class="card-body" style="background-color: {color}">
|
||||||
<a href="/client/{clientId}" use:link role="button" class="btn btn-secondary material-icons float-right">edit</a>
|
<a href="/client/{clientId}" use:link replace role="button" class="btn btn-secondary material-icons float-right">edit</a>
|
||||||
<i class="material-icons" aria-hidden="true">devices</i>
|
<i class="material-icons" aria-hidden="true">devices</i>
|
||||||
<h4 class="card-title">{dev.Name}</h4>
|
<h4 class="card-title">{dev.Name}</h4>
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
|
|||||||
@@ -2,11 +2,10 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import Client from './Client.svelte';
|
import Client from './Client.svelte';
|
||||||
|
|
||||||
let user = "anonymous";
|
export let user;
|
||||||
|
|
||||||
let clients = [];
|
|
||||||
|
|
||||||
let clientsUrl = "/api/v1/users/" + user + "/clients";
|
let clientsUrl = "/api/v1/users/" + user + "/clients";
|
||||||
|
let clients = [];
|
||||||
|
|
||||||
async function getClients() {
|
async function getClients() {
|
||||||
const res = await fetch(clientsUrl);
|
const res = await fetch(clientsUrl);
|
||||||
@@ -26,7 +25,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<h2>My Clients</h2>
|
<h2>My Clients <small class="text-muted">({user})</small></h2>
|
||||||
|
|
||||||
<ul class="list-unstyled">
|
<ul class="list-unstyled">
|
||||||
{#each clients as dev}
|
{#each clients as dev}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import Cookie from "cookie-universal";
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { link, navigate } from "svelte-routing";
|
import { link, navigate } from "svelte-routing";
|
||||||
|
|
||||||
export let clientId;
|
export let clientId;
|
||||||
|
|
||||||
let clientUrl = `/api/v1/users/` + user + `/clients/` + clientId;
|
const user = Cookie().get("wguser", { fromRes: true});
|
||||||
|
|
||||||
let user = "anonymous";
|
const clientUrl = `/api/v1/users/` + user + `/clients/` + clientId;
|
||||||
|
|
||||||
let client = {};
|
let client = {};
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
body: JSON.stringify(client),
|
body: JSON.stringify(client),
|
||||||
});
|
});
|
||||||
client = await res.json();
|
client = await res.json();
|
||||||
|
navigate("/", { replace: true });
|
||||||
console.log("Saved changes", res);
|
console.log("Saved changes", res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user