mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2025-07-13 16:46:58 +00:00
Spent 4 hours working on OIDC verification
This commit is contained in:
parent
66bd1da571
commit
380b9a73ab
@ -1,8 +1,10 @@
|
||||
from tzlocal import get_localzone
|
||||
from authlib.integrations.flask_client import OAuth
|
||||
import authlib
|
||||
|
||||
from functools import wraps
|
||||
|
||||
from flask import Blueprint, render_template, abort, request, Flask, current_app, session
|
||||
from flask import Blueprint, render_template, abort, request, Flask, current_app, session, redirect, url_for
|
||||
import os
|
||||
|
||||
from modules.WireguardConfiguration import WireguardConfiguration
|
||||
@ -28,6 +30,10 @@ def login_required(f):
|
||||
|
||||
def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration], dashboardConfig: DashboardConfig):
|
||||
from modules.DashboardClients import DashboardClients
|
||||
from modules.DashboardOIDC import DashboardOIDC
|
||||
|
||||
|
||||
OIDC = DashboardOIDC()
|
||||
DashboardClients = DashboardClients(wireguardConfigurations)
|
||||
client = Blueprint('client', __name__, template_folder=os.path.abspath("./static/client/dist"))
|
||||
prefix = f'{dashboardConfig.GetConfig("Server", "app_prefix")[1]}/client'
|
||||
@ -38,13 +44,19 @@ def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration],
|
||||
if request.method.lower() == 'options':
|
||||
return ResponseObject(True)
|
||||
|
||||
|
||||
@client.post(f'{prefix}/api/signup')
|
||||
def ClientAPI_SignUp():
|
||||
data = request.json
|
||||
status, msg = DashboardClients.SignUp(**data)
|
||||
return ResponseObject(status, msg)
|
||||
|
||||
@client.post(f'{prefix}/api/signin/oidc/')
|
||||
def ClientAPI_SignIn_OIDC_Google():
|
||||
data = request.json
|
||||
print(OIDC.VerifyToken(**data))
|
||||
|
||||
return ResponseObject()
|
||||
|
||||
@client.post(f'{prefix}/api/signin')
|
||||
def ClientAPI_SignIn():
|
||||
data = request.json
|
||||
|
@ -14,8 +14,9 @@ from .Utilities import (
|
||||
from .DashboardAPIKey import DashboardAPIKey
|
||||
|
||||
|
||||
|
||||
class DashboardConfig:
|
||||
DashboardVersion = 'v4.2.3'
|
||||
DashboardVersion = 'v4.3'
|
||||
ConfigurationPath = os.getenv('CONFIGURATION_PATH', '.')
|
||||
ConfigurationFilePath = os.path.join(ConfigurationPath, 'wg-dashboard.ini')
|
||||
|
||||
|
84
src/modules/DashboardOIDC.py
Normal file
84
src/modules/DashboardOIDC.py
Normal file
@ -0,0 +1,84 @@
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
from jose import jwt
|
||||
|
||||
|
||||
class DashboardOIDC:
|
||||
ConfigurationPath = os.getenv('CONFIGURATION_PATH', '.')
|
||||
ConfigurationFilePath = os.path.join(ConfigurationPath, 'wg-dashboard-oidc-providers.json')
|
||||
def __init__(self):
|
||||
self.providers: dict[str, dict] = None
|
||||
self.__default = {
|
||||
'Provider': {
|
||||
'client_id': '',
|
||||
'client_secret': '',
|
||||
'issuer': '',
|
||||
},
|
||||
}
|
||||
|
||||
if not os.path.exists(DashboardOIDC.ConfigurationFilePath):
|
||||
with open(DashboardOIDC.ConfigurationFilePath, "w+") as f:
|
||||
encoder = json.JSONEncoder(indent=4)
|
||||
f.write(encoder.encode(self.__default))
|
||||
|
||||
self.ReadFile()
|
||||
|
||||
def VerifyToken(self, provider, code, redirect_uri):
|
||||
if not all([provider, code, redirect_uri]):
|
||||
return False, ""
|
||||
|
||||
if provider not in self.providers.keys():
|
||||
return False, "Provider does not exist"
|
||||
|
||||
provider = self.providers.get(provider)
|
||||
oidc_config = requests.get(f"{provider.get('issuer')}.well-known/openid-configuration").json()
|
||||
|
||||
data = {
|
||||
"grant_type": "authorization_code",
|
||||
"code": code,
|
||||
"redirect_uri": redirect_uri,
|
||||
"client_id": provider.get('client_id'),
|
||||
"client_secret": provider.get('client_secret')
|
||||
}
|
||||
|
||||
tokens = requests.post(oidc_config.get('token_endpoint'), data=data).json()
|
||||
|
||||
|
||||
id_token = tokens.get('id_token')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
jwks_uri = oidc_config.get("jwks_uri")
|
||||
issuer = oidc_config.get("issuer")
|
||||
jwks = requests.get(jwks_uri).json()
|
||||
|
||||
from jose.utils import base64url_decode
|
||||
from jose.backends.cryptography_backend import CryptographyRSAKey
|
||||
|
||||
# Choose the right key based on `kid` in token header
|
||||
headers = jwt.get_unverified_header(id_token)
|
||||
kid = headers["kid"]
|
||||
|
||||
# Find the key with the correct `kid`
|
||||
key = next(k for k in jwks["keys"] if k["kid"] == kid)
|
||||
|
||||
# Use the key to verify token
|
||||
payload = jwt.decode(
|
||||
id_token,
|
||||
key,
|
||||
algorithms=[key["alg"]],
|
||||
audience=provider.get('client_id'),
|
||||
issuer=issuer
|
||||
)
|
||||
|
||||
print(payload) # This contains the user's claims
|
||||
|
||||
|
||||
def ReadFile(self):
|
||||
decoder = json.JSONDecoder()
|
||||
self.providers = decoder.decode(
|
||||
open(DashboardOIDC.ConfigurationFilePath, 'r').read()
|
||||
)
|
@ -13,3 +13,4 @@ sqlalchemy_utils
|
||||
psycopg2
|
||||
mysqlclient
|
||||
tzlocal
|
||||
python-jose
|
@ -5,7 +5,7 @@ import "animate.css"
|
||||
import PeerSettingsDropdown from "@/components/configurationComponents/peerSettingsDropdown.vue";
|
||||
import LocaleText from "@/components/text/localeText.vue";
|
||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||
import {GetLocale} from "../../utilities/locale.js";
|
||||
import {GetLocale} from "@/utilities/locale.js";
|
||||
|
||||
export default {
|
||||
name: "peer",
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
src/static/client/dist/client.html
vendored
4
src/static/client/dist/client.html
vendored
@ -5,8 +5,8 @@
|
||||
<link rel="icon" href="/static/client/dist/img/Logo-2-128x128.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
<script type="module" crossorigin src="/static/client/dist/assets/index-C7xxuubz.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/static/client/dist/assets/index-DtDMqk5z.css">
|
||||
<script type="module" crossorigin src="/static/client/dist/assets/index-VLGPfnEQ.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/static/client/dist/assets/index-BmGX7v2g.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
@ -4,7 +4,9 @@
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/img/Logo-2-128x128.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
<title>WGDashboard Client</title>
|
||||
<script src="https://apis.google.com/js/platform.js" async defer></script>
|
||||
<meta name="google-signin-client_id" content="140556036774-66pjm1qkml0v27ru1utsoeftsv3b4hid.apps.googleusercontent.com">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
22
src/static/client/package-lock.json
generated
22
src/static/client/package-lock.json
generated
@ -12,6 +12,7 @@
|
||||
"bootstrap": "^5.3.6",
|
||||
"bootstrap-icons": "^1.13.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"oidc-client-ts": "^3.2.1",
|
||||
"pinia": "^3.0.2",
|
||||
"qrcode": "^1.5.4",
|
||||
"uuid": "^11.1.0",
|
||||
@ -2524,6 +2525,15 @@
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/jwt-decode": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/jwt-decode/-/jwt-decode-4.0.0.tgz",
|
||||
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/kolorist": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmmirror.com/kolorist/-/kolorist-1.8.0.tgz",
|
||||
@ -2670,6 +2680,18 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/oidc-client-ts": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/oidc-client-ts/-/oidc-client-ts-3.2.1.tgz",
|
||||
"integrity": "sha512-hS5AJ5s/x4bXhHvNJT1v+GGvzHUwdRWqNQQbSrp10L1IRmzfRGKQ3VWN3dstJb+oF3WtAyKezwD2+dTEIyBiAA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"jwt-decode": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/open": {
|
||||
"version": "10.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/open/-/open-10.1.2.tgz",
|
||||
|
@ -13,6 +13,7 @@
|
||||
"bootstrap": "^5.3.6",
|
||||
"bootstrap-icons": "^1.13.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"oidc-client-ts": "^3.2.1",
|
||||
"pinia": "^3.0.2",
|
||||
"qrcode": "^1.5.4",
|
||||
"uuid": "^11.1.0",
|
||||
|
@ -39,6 +39,16 @@ const route = useRoute()
|
||||
if (route.query.Email){
|
||||
formData.Email = route.query.Email
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
client_id: "ijDjDnBCDuA75srtsq7ksxwpZkLjxiRZVdmkWnRC",
|
||||
redirect_uri: window.location.protocol + '//' + window.location.host + window.location.pathname,
|
||||
response_type: 'code',
|
||||
state: 'Authentik',
|
||||
scope: 'openid email profile'
|
||||
}).toString()
|
||||
console.log(params)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -47,6 +57,19 @@ if (route.query.Email){
|
||||
<h1 class="display-4">Welcome back</h1>
|
||||
<p class="text-muted">Sign in to access your <strong>WGDashboard Client</strong> account</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<a class="btn btn-sm btn-outline-body rounded-3"
|
||||
:href="'http://178.128.231.4:9000/application/o/authorize/?' + params"
|
||||
style="flex: 1 1 0px;" >
|
||||
Google
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-body rounded-3" style="flex: 1 1 0px;">
|
||||
GitHub
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-body rounded-3" style="flex: 1 1 0px;">
|
||||
Facebook
|
||||
</button>
|
||||
</div>
|
||||
<form class="mt-4 d-flex flex-column gap-3" @submit="e => signIn(e)">
|
||||
<div class="form-floating">
|
||||
<input type="text"
|
||||
|
@ -7,12 +7,24 @@ import {createPinia} from "pinia";
|
||||
|
||||
import 'bootstrap/dist/js/bootstrap.bundle.js'
|
||||
import {clientStore} from "@/stores/clientStore.js";
|
||||
import {OIDCAuth} from "@/utilities/oidcAuth.js";
|
||||
import {axiosPost} from "@/utilities/request.js";
|
||||
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const state = params.get('state')
|
||||
const code = params.get('code')
|
||||
|
||||
if (state && code){
|
||||
axiosPost("/api/signin/oidc/", {
|
||||
provider: state,
|
||||
code: code,
|
||||
redirect_uri: window.location.protocol + '//' + window.location.host + window.location.pathname
|
||||
}).then(data => {
|
||||
console.log(data)
|
||||
})
|
||||
}
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
|
||||
|
||||
|
||||
app.use(router)
|
||||
app.mount("#app")
|
6
src/static/client/src/utilities/oidcAuth.js
Normal file
6
src/static/client/src/utilities/oidcAuth.js
Normal file
@ -0,0 +1,6 @@
|
||||
export const OIDCAuth = async () => {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const state = params.get('state')
|
||||
const code = params.get('code')
|
||||
|
||||
}
|
@ -18,6 +18,7 @@ export default defineConfig({
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
|
Loading…
x
Reference in New Issue
Block a user