mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2025-07-12 16:16:58 +00:00
OIDC should be good to go
This commit is contained in:
parent
bf74150f62
commit
aa66a5ffb2
@ -21,7 +21,7 @@ def ResponseObject(status=True, message=None, data=None, status_code = 200) -> F
|
||||
def login_required(f):
|
||||
@wraps(f)
|
||||
def func(*args, **kwargs):
|
||||
if session.get("Email") is None or session.get("totpVerified") is None or not session.get("totpVerified") or session.get("role") != "client":
|
||||
if session.get("Email") is None or session.get("TotpVerified") is None or not session.get("TotpVerified") or session.get("Role") != "client":
|
||||
return ResponseObject(False, "Unauthorized access.", data=None, status_code=401)
|
||||
return f(*args, **kwargs)
|
||||
return func
|
||||
@ -60,8 +60,8 @@ def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration],
|
||||
return ResponseObject(status, oidcData)
|
||||
|
||||
session['Email'] = oidcData.get('email')
|
||||
session['role'] = 'client'
|
||||
session['totpVerified'] = True
|
||||
session['Role'] = 'client'
|
||||
session['TotpVerified'] = True
|
||||
|
||||
return ResponseObject()
|
||||
|
||||
@ -71,15 +71,15 @@ def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration],
|
||||
status, msg = DashboardClients.SignIn(**data)
|
||||
if status:
|
||||
session['Email'] = data.get('Email')
|
||||
session['role'] = 'client'
|
||||
session['totpVerified'] = False
|
||||
session['Role'] = 'client'
|
||||
session['TotpVerified'] = False
|
||||
return ResponseObject(status, msg)
|
||||
|
||||
@client.get(f'{prefix}/api/signout')
|
||||
def ClientAPI_SignOut():
|
||||
session['Email'] = None
|
||||
session['role'] = None
|
||||
session['totpVerified'] = None
|
||||
if session.get("SignInMethod") == "OIDC":
|
||||
DashboardClients.SignOut_OIDC()
|
||||
session.clear()
|
||||
return ResponseObject(True)
|
||||
|
||||
@client.get(f'{prefix}/api/signin/totp')
|
||||
@ -102,7 +102,7 @@ def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration],
|
||||
if status:
|
||||
if session.get('Email') is None:
|
||||
return ResponseObject(False, "Sign in status is invalid", status_code=401)
|
||||
session['totpVerified'] = True
|
||||
session['TotpVerified'] = True
|
||||
return ResponseObject(True, data={
|
||||
"Email": session.get('Email'),
|
||||
"Profile": DashboardClients.GetClientProfile(session.get("ClientID"))
|
||||
|
@ -4,6 +4,7 @@ import uuid
|
||||
import bcrypt
|
||||
import pyotp
|
||||
import sqlalchemy as db
|
||||
import requests
|
||||
|
||||
from .ConnectionString import ConnectionString
|
||||
from .DashboardClientsPeerAssignment import DashboardClientsPeerAssignment
|
||||
@ -133,19 +134,34 @@ class DashboardClients:
|
||||
return True, newClientUUID
|
||||
return False, "User already signed up"
|
||||
|
||||
def SignOut_OIDC(self):
|
||||
sessionPayload = session.get('OIDCPayload')
|
||||
status, oidc_config = self.OIDC.GetProviderConfiguration(session.get('SignInPayload').get("Provider"))
|
||||
signOut = requests.get(
|
||||
oidc_config.get("end_session_endpoint"),
|
||||
params={
|
||||
'id_token_hint': session.get('SignInPayload').get("Payload").get('sid')
|
||||
}
|
||||
)
|
||||
return True
|
||||
|
||||
def SignIn_OIDC(self, **kwargs):
|
||||
status, data = self.OIDC.VerifyToken(**kwargs)
|
||||
if not status:
|
||||
return False, "Sign in failed"
|
||||
return False, "Sign in failed. Reason: " + data
|
||||
existingClient = self.SignIn_OIDC_UserExistence(data)
|
||||
if not existingClient:
|
||||
status, newClientUUID = self.SignUp_OIDC(data)
|
||||
session['ClientID'] = newClientUUID
|
||||
else:
|
||||
session['ClientID'] = existingClient.get("ClientID")
|
||||
|
||||
session['SignInMethod'] = 'OIDC'
|
||||
session['SignInPayload'] = {
|
||||
"Provider": kwargs.get('provider'),
|
||||
"Payload": data
|
||||
}
|
||||
return True, data
|
||||
|
||||
|
||||
def SignIn(self, Email, Password) -> tuple[bool, str]:
|
||||
if not all([Email, Password]):
|
||||
return False, "Please fill in all fields"
|
||||
@ -153,6 +169,7 @@ class DashboardClients:
|
||||
if existingClient:
|
||||
checkPwd = self.SignIn_ValidatePassword(Email, Password)
|
||||
if checkPwd:
|
||||
session['SignInMethod'] = 'local'
|
||||
session['Email'] = Email
|
||||
session['ClientID'] = existingClient.get("ClientID")
|
||||
return True, self.DashboardClientsTOTP.GenerateToken(existingClient.get("ClientID"))
|
||||
|
@ -10,6 +10,7 @@ class DashboardOIDC:
|
||||
ConfigurationFilePath = os.path.join(ConfigurationPath, 'wg-dashboard-oidc-providers.json')
|
||||
def __init__(self):
|
||||
self.providers: dict[str, dict] = {}
|
||||
self.provider_secret: dict[str, str] = {}
|
||||
self.__default = {
|
||||
'Provider': {
|
||||
'client_id': '',
|
||||
@ -26,52 +27,36 @@ class DashboardOIDC:
|
||||
self.ReadFile()
|
||||
|
||||
def GetProviders(self):
|
||||
providers = {}
|
||||
for k in self.providers.keys():
|
||||
if all([self.providers[k]['client_id'], self.providers[k]['client_secret'], self.providers[k]['issuer']]):
|
||||
try:
|
||||
print("Requesting " + f"{self.providers[k]['issuer'].strip('/')}/.well-known/openid-configuration")
|
||||
oidc_config = requests.get(
|
||||
f"{self.providers[k]['issuer'].strip('/')}/.well-known/openid-configuration",
|
||||
verify=certifi.where()
|
||||
).json()
|
||||
providers[k] = {
|
||||
'client_id': self.providers[k]['client_id'],
|
||||
'issuer': self.providers[k]['issuer'].strip('/')
|
||||
}
|
||||
except Exception as e:
|
||||
current_app.logger.error("Failed to request OIDC config for this provider: " + self.providers[k]['issuer'].strip('/'), exc_info=e)
|
||||
|
||||
return providers
|
||||
return self.providers
|
||||
|
||||
def VerifyToken(self, provider, code, redirect_uri):
|
||||
try:
|
||||
if not all([provider, code, redirect_uri]):
|
||||
return False, ""
|
||||
return False, "Please provide all parameters"
|
||||
|
||||
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').strip('/')}/.well-known/openid-configuration",
|
||||
verify=certifi.where()
|
||||
|
||||
).json()
|
||||
secrete = self.provider_secret.get(provider)
|
||||
oidc_config_status, oidc_config = self.GetProviderConfiguration(provider)
|
||||
provider_info = self.providers.get(provider)
|
||||
|
||||
|
||||
data = {
|
||||
"grant_type": "authorization_code",
|
||||
"code": code,
|
||||
"redirect_uri": redirect_uri,
|
||||
"client_id": provider.get('client_id'),
|
||||
"client_secret": provider.get('client_secret')
|
||||
"client_id": provider_info.get('client_id'),
|
||||
"client_secret": secrete
|
||||
}
|
||||
|
||||
try:
|
||||
tokens = requests.post(oidc_config.get('token_endpoint'), data=data).json()
|
||||
if not all([tokens.get('access_token'), tokens.get('id_token')]):
|
||||
print(oidc_config.get('token_endpoint'), data)
|
||||
return False, tokens.get('error_description', None)
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
return False, str(e)
|
||||
|
||||
access_token = tokens.get('access_token')
|
||||
@ -84,31 +69,58 @@ class DashboardOIDC:
|
||||
kid = headers["kid"]
|
||||
|
||||
key = next(k for k in jwks["keys"] if k["kid"] == kid)
|
||||
|
||||
print(key)
|
||||
|
||||
|
||||
payload = jwt.decode(
|
||||
id_token,
|
||||
key,
|
||||
algorithms=[key["alg"]],
|
||||
audience=provider.get('client_id'),
|
||||
audience=provider_info.get('client_id'),
|
||||
issuer=issuer,
|
||||
access_token=access_token
|
||||
)
|
||||
|
||||
print(payload)
|
||||
return True, payload
|
||||
except Exception as e:
|
||||
current_app.logger.error('Read OIDC file failed. Reason: ' + str(e), provider, code, redirect_uri)
|
||||
return False, str(e)
|
||||
|
||||
|
||||
def GetProviderConfiguration(self, provider_name):
|
||||
if not all([provider_name]):
|
||||
return False, None
|
||||
provider = self.providers.get(provider_name)
|
||||
try:
|
||||
oidc_config = requests.get(
|
||||
f"{provider.get('issuer').strip('/')}/.well-known/openid-configuration",
|
||||
verify=certifi.where()
|
||||
).json()
|
||||
except Exception as e:
|
||||
current_app.logger.error("Failed to get OpenID Configuration of " + provider.get('issuer'), exc_info=e)
|
||||
return False, None
|
||||
return True, oidc_config
|
||||
|
||||
def ReadFile(self):
|
||||
decoder = json.JSONDecoder()
|
||||
try:
|
||||
self.providers = decoder.decode(
|
||||
providers = decoder.decode(
|
||||
open(DashboardOIDC.ConfigurationFilePath, 'r').read()
|
||||
)
|
||||
print(self.providers)
|
||||
for k in providers.keys():
|
||||
if all([providers[k]['client_id'], providers[k]['client_secret'], providers[k]['issuer']]):
|
||||
try:
|
||||
print("Requesting " + f"{providers[k]['issuer'].strip('/')}/.well-known/openid-configuration")
|
||||
oidc_config = requests.get(
|
||||
f"{providers[k]['issuer'].strip('/')}/.well-known/openid-configuration",
|
||||
timeout=3,
|
||||
verify=certifi.where()
|
||||
).json()
|
||||
self.providers[k] = {
|
||||
'client_id': providers[k]['client_id'],
|
||||
'issuer': providers[k]['issuer'].strip('/'),
|
||||
'openid_configuration': oidc_config
|
||||
}
|
||||
self.provider_secret[k] = providers[k]['client_secret']
|
||||
except Exception as e:
|
||||
current_app.logger.error("Failed to request OIDC config for this provider: " + providers[k]['issuer'].strip('/'), exc_info=e)
|
||||
except Exception as e:
|
||||
current_app.logger.error('Read OIDC file failed. Reason: ' + str(e))
|
||||
return False
|
@ -21,13 +21,22 @@ const initApp = () => {
|
||||
app.mount("#app")
|
||||
}
|
||||
|
||||
function removeSearchString() {
|
||||
let url = new URL(window.location.href);
|
||||
url.search = ''; // Remove all query parameters
|
||||
history.replaceState({}, document.title, url.toString());
|
||||
}
|
||||
|
||||
if (state && code){
|
||||
axiosPost("/api/signin/oidc", {
|
||||
provider: state,
|
||||
code: code,
|
||||
redirect_uri: window.location.protocol + '//' + window.location.host + window.location.pathname
|
||||
}).then(data => {
|
||||
window.location.search = ''
|
||||
let url = new URL(window.location.href);
|
||||
url.search = '';
|
||||
history.replaceState({}, document.title, url.toString());
|
||||
|
||||
initApp()
|
||||
if (!data.status){
|
||||
const store = clientStore()
|
||||
|
@ -1,9 +1,10 @@
|
||||
<script setup async>
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
import {axiosGet} from "@/utilities/request.js";
|
||||
import {axiosGet, requestURl} from "@/utilities/request.js";
|
||||
import {clientStore} from "@/stores/clientStore.js";
|
||||
import Configuration from "@/components/Configuration/configuration.vue";
|
||||
import {onBeforeRouteLeave} from "vue-router";
|
||||
import {onBeforeRouteLeave, useRouter} from "vue-router";
|
||||
import axios from "axios";
|
||||
const store = clientStore()
|
||||
const loading = ref(true)
|
||||
|
||||
@ -23,7 +24,20 @@ onMounted(async () => {
|
||||
|
||||
onBeforeRouteLeave(() => {
|
||||
clearInterval(refreshInterval.value)
|
||||
})
|
||||
});
|
||||
const router = useRouter()
|
||||
const signingOut = ref(false)
|
||||
const signOut = async () => {
|
||||
clearInterval(refreshInterval.value)
|
||||
signingOut.value = true;
|
||||
await axios.get(requestURl('/api/signout')).then(() => {
|
||||
router.push('/signin')
|
||||
}).catch(() => {
|
||||
router.push('/signin')
|
||||
});
|
||||
store.newNotification("Sign out successful", "success")
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -37,10 +51,14 @@ onBeforeRouteLeave(() => {
|
||||
<i class="bi bi-gear-fill me-sm-2"></i>
|
||||
<span>Settings</span>
|
||||
</RouterLink>
|
||||
<RouterLink to="/signout" class="btn btn-outline-danger rounded-3 btn-sm" aria-current="page">
|
||||
<a role="button" @click="signOut()" class="btn btn-outline-danger rounded-3 btn-sm"
|
||||
:class="{disabled: signingOut}"
|
||||
aria-current="page">
|
||||
<i class="bi bi-box-arrow-left me-sm-2"></i>
|
||||
<span>Sign Out</span>
|
||||
</RouterLink>
|
||||
<span>
|
||||
{{ signingOut ? 'Signing out...':'Sign Out'}}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user