Compare commits

...

6 commits

Author SHA1 Message Date
4a7b66b481 feat: frontend 2026-05-08 16:45:33 +01:00
875c0cc258 feat: static file mounting for frontend 2026-05-08 16:44:40 +01:00
2fe455ee81 feat: domain details endpoint
Needs a response model to filter response data
2026-05-08 16:44:12 +01:00
a86047ec79 feat: metrics endpoint for frontend 2026-05-08 16:43:37 +01:00
3a1e822b63 feat: authed users in env 2026-05-07 12:56:30 +01:00
5f069285c2 fix: auth port to joserfc
authlib.jose is deprecated and no longer works with other updated dependencies.
2026-05-07 12:54:55 +01:00
12 changed files with 891 additions and 16 deletions

View file

@ -9,5 +9,7 @@ class AuthConfig(CustomBaseSettings):
OIDC_AUDIENCE: str = "" OIDC_AUDIENCE: str = ""
CLIENT_ID: str = "" CLIENT_ID: str = ""
AUTHORISED_USERS: list[str] = []
auth_settings = AuthConfig() auth_settings = AuthConfig()

View file

@ -6,16 +6,15 @@ Exports:
- authed_dependency - authed_dependency
""" """
import json import json
import requests
from typing import Annotated from typing import Annotated, Any
from authlib.jose import jwt from joserfc import jwt
from urllib.request import urlopen from urllib.request import urlopen
from fastapi import Depends, HTTPException from fastapi import Depends, HTTPException
from fastapi.security import OpenIdConnect from fastapi.security import OpenIdConnect
from authlib.jose.rfc7517.jwk import JsonWebKey from joserfc.jwk import KeySet
from authlib.jose.rfc7517.key_set import KeySet
from authlib.oauth2.rfc7523.validator import JWTBearerToken
from src.auth.config import auth_settings from src.auth.config import auth_settings
@ -24,12 +23,12 @@ oidc = OpenIdConnect(openIdConnectUrl=auth_settings.OIDC_CONFIG)
oidc_dependency = Annotated[str, Depends(oidc)] oidc_dependency = Annotated[str, Depends(oidc)]
async def get_current_user(oidc_auth_string: oidc_dependency) -> JWTBearerToken: async def get_current_user(oidc_auth_string: oidc_dependency) -> dict[str, Any]:
config_url = urlopen(auth_settings.OIDC_CONFIG) config_url = urlopen(auth_settings.OIDC_CONFIG)
config = json.loads(config_url.read()) config = json.loads(config_url.read())
jwks_uri = config["jwks_uri"] jwks_uri = config["jwks_uri"]
key_response = urlopen(jwks_uri) key_response = requests.get(jwks_uri)
jwk_keys: KeySet = JsonWebKey.import_key_set(json.loads(key_response.read())) jwk_keys = KeySet.import_key_set(key_response.json())
claims_options = { claims_options = {
"exp": {"essential": True}, "exp": {"essential": True},
@ -37,23 +36,23 @@ async def get_current_user(oidc_auth_string: oidc_dependency) -> JWTBearerToken:
"iss": {"essential": True, "value": auth_settings.OIDC_ISSUER}, "iss": {"essential": True, "value": auth_settings.OIDC_ISSUER},
} }
claims: JWTBearerToken = jwt.decode( token = jwt.decode(
oidc_auth_string.replace("Bearer ", ""), oidc_auth_string.replace("Bearer ", ""),
jwk_keys, jwk_keys
claims_options=claims_options,
claims_cls=JWTBearerToken,
) )
claims.validate() claims_requests = jwt.JWTClaimsRegistry(**claims_options)
return claims claims_requests.validate(token.claims)
return token.claims
claims_dependency = Annotated[JWTBearerToken, Depends(get_current_user)] claims_dependency = Annotated[dict[str, Any], Depends(get_current_user)]
async def is_authed_user(claims: claims_dependency) -> bool: async def is_authed_user(claims: claims_dependency) -> bool:
authed_users: list[str] = ["chris@sr2.uk"] authed_users: list[str] = auth_settings.AUTHORISED_USERS
user_email = claims.get("email", None) user_email = claims.get("email", None)
if not user_email or user_email not in authed_users: if not user_email or user_email not in authed_users:
raise HTTPException(status_code=403, detail="Not authenticated") raise HTTPException(status_code=403, detail="Not authenticated")

View file

@ -6,6 +6,7 @@ from typing import AsyncGenerator
from prometheus_client import make_asgi_app from prometheus_client import make_asgi_app
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from starlette.middleware.sessions import SessionMiddleware from starlette.middleware.sessions import SessionMiddleware
from starlette.middleware.cors import CORSMiddleware from starlette.middleware.cors import CORSMiddleware
@ -64,4 +65,6 @@ app.include_router(api_router)
app.misp_handler = MISPHandler() app.misp_handler = MISPHandler()
app.mount('/', StaticFiles(directory='src/static',html=True))
prometheus.APP_STATE.state("running") prometheus.APP_STATE.state("running")

View file

@ -10,6 +10,7 @@ from sqlalchemy.sql import and_
from src.auth.service import authed_dependency from src.auth.service import authed_dependency
from src.database import db_dependency from src.database import db_dependency
from src.prometheus import prometheus
from src.misp.models import Domain from src.misp.models import Domain
from src.misp.schemas import MispUpdatePutRequest, MispUpdatePutResponse from src.misp.schemas import MispUpdatePutRequest, MispUpdatePutResponse
@ -20,6 +21,21 @@ router = APIRouter(
) )
@router.get("/dashboard_metrics", include_in_schema=False)
async def dashboard_metrics():
"""
Endpoint for UI status bar. JSONifies required Prometheus metrics.
:return:
"""
return {
"blocked_domain_count": prometheus.blocked_domain_count._value.get(),
"TIMER_STATE": prometheus.TIMER_STATE._value,
"MISP_STATE": prometheus.MISP_STATE._value,
"last_update_complete_time": prometheus.last_update_complete_time._value.get(),
"last_update_length": prometheus.last_update_length._value.get(),
}
@router.put("/manual_update", response_model=MispUpdatePutResponse) @router.put("/manual_update", response_model=MispUpdatePutResponse)
async def manual_misp_update(request: Request, update_request: MispUpdatePutRequest, background_tasks: BackgroundTasks): async def manual_misp_update(request: Request, update_request: MispUpdatePutRequest, background_tasks: BackgroundTasks):
published_time = update_request.published_timestamp published_time = update_request.published_timestamp
@ -121,3 +137,9 @@ async def event_reinstate(domain: str, db: db_dependency, event: int):
db.commit() db.commit()
return {"status": "Event Un-ignored"} return {"status": "Event Un-ignored"}
@router.get("/domain/details/{domain}")
async def domain_details(db: db_dependency, domain: str):
result = db.query(Domain).filter(Domain.domain==domain).first()
return result

View file

@ -0,0 +1,556 @@
class Auth {
constructor() {
this.url = "http://localhost:8000"
this.settings = {
authority: "https://sso.sr2.uk/realms/sr2",
client_id: "chris-dev",
redirect_uri: this.url + "/signin-callback.html",
response_type: "code",
scope: "openid email profile",
response_mode: "fragment",
}
this.user_manager = new oidc.UserManager(this.settings)
this.get_user().then(() => {
window.is_authenticated = Boolean(this.user);
ui.update_auth_button()
})
}
async get_user() {
this.user = await this.user_manager.getUser()
}
async login() {
try {
this.user = await this.user_manager.signinPopup();
window.is_authenticated = Boolean(auth.user);
} catch (error) {
console.error(error);
}
}
async logout() {
try {
this.user = await this.user_manager.signoutPopup();
window.is_authenticated = Boolean(auth.user);
} catch (error) {
console.error(error);
}
}
}
class UI {
constructor() {
this.auth_button = document.getElementById("auth_button");
this.blocked_domains = document.getElementById("blocked_domains")
this.timer_status = document.getElementById("timer_status")
this.overall_status = document.getElementById("overall_status")
this.last_update_complete_time = document.getElementById("last_update_complete_time")
this.last_update_duration = document.getElementById("last_update_duration")
this.manual_update_btn = document.getElementById("manual_update_btn")
this.start_timer_btn = document.getElementById("start_timer_btn")
this.stop_timer_btn = document.getElementById("stop_timer_btn")
this.domain_search_btn = document.getElementById("domain_search_btn")
this.false_positive_load_btn = document.getElementById("false_positive_load_btn")
this.event_container = document.getElementById("events_container")
this.create_listeners()
}
create_listeners() {
this.auth_button.addEventListener("click", this.onclick_auth_button)
this.manual_update_btn.addEventListener("click", this.onclick_manual_update_btn)
this.start_timer_btn.addEventListener("click", this.onclick_start_timer_btn)
this.stop_timer_btn.addEventListener("click", this.onclick_stop_timer_btn)
this.domain_search_btn.addEventListener("click", this.onclick_domain_search_btn)
this.false_positive_load_btn.addEventListener("click", this.onclick_false_positive_load_btn)
this.event_container.addEventListener("click", this.onclick_event_button)
document.getElementById("modal_button").addEventListener("click", this.onclick_modal_button)
}
open_modal(title, message) {
document.getElementById("modal_title").innerText = title;
document.getElementById("modal_message").innerText = message;
document.getElementById("modal_backdrop").style.display = "flex";
}
set_loading(button_id, loading) {
const button = document.getElementById(button_id);
button.disabled = loading;
if (loading) {
button.dataset.original_text = button.innerText;
button.innerText = "Loading...";
} else {
button.innerText = button.dataset.original_text;
}
}
sync_timer_buttons() {
this.start_timer_btn.disabled = timer_running;
this.stop_timer_btn.disabled = !timer_running;
}
render_search_results(results) {
const container = document.getElementById("domain_search_results");
const result_count = Object.keys(results).length;
if (!result_count) {
container.innerHTML = `<div class="search-result">No results found.</div>`;
return;
}
container.innerHTML = "";
for (const [key, value] of Object.entries(results)) {
const row = document.createElement("div");
row.className = "search-result";
row.innerHTML = `
<div class="row">
<div class="col-8">
${key}
</div>
<div class="col-4 ${ui.event_status_class(value)}">
${value}
</div>
</div>
`;
container.appendChild(row);
}
}
onclick_auth_button() {
if (is_authenticated) {
auth.logout().then(() => {
ui.update_auth_button()
})
} else {
auth.login().then(() => {
ui.update_auth_button()
})
}
ui.update_auth_button()
}
update_auth_button() {
const button = document.getElementById("auth_button");
if (is_authenticated) {
button.innerText = "Logged In";
button.className = "button default-dm success";
} else {
button.innerText = "Logged Out";
button.className = "button default-dm secondary";
}
}
event_status_class(value) {
switch (value.toUpperCase()) {
case "ALLOWED":
return "status-good";
case "IGNORED":
return "status-warning";
default:
return "status-bad";
}
}
update_metrics(metrics){
this.blocked_domains.innerHTML = metrics.blocked_domain_count;
let date
let pretty_length
if(!metrics.last_update_complete_time){
date = "No update since last restart"
pretty_length = ""
} else {
date = new Date(metrics.last_update_complete_time * 1000).toLocaleString();
pretty_length = `${Math.round(metrics.last_update_length)} seconds`
}
this.last_update_complete_time.innerHTML = date;
this.last_update_duration.innerHTML = pretty_length;
let timer_value = ""
switch (metrics.TIMER_STATE) {
case 0:
timer_value = "RUNNING"
window.timer_running = true;
break;
case 1:
timer_value = "STOPPING"
window.timer_running = true;
break;
case 2:
timer_value = "STOPPED"
window.timer_running = false;
break;
}
this.timer_status.innerHTML = timer_value;
this.timer_status.className = timer_running ? "status-good" : "status-warning";
this.sync_timer_buttons()
let overall_status = ""
switch (metrics.MISP_STATE) {
case 0:
overall_status = "IDLE"
break;
case 1:
overall_status = "FETCHING"
break;
case 2:
overall_status = "UPDATING"
break;
case 3:
overall_status = "RELOADING"
break;
case 4:
overall_status = "ERROR"
break;
}
this.overall_status.innerHTML = overall_status;
}
render_false_positive_controls(domain_data) {
document.getElementById("false_positive_actions").style.display = "block";
const always_allow_btn = document.getElementById("always_allow_btn");
always_allow_btn.innerText = domain_data.always_allow ? "Enabled" : "Disabled";
always_allow_btn.className = domain_data.always_allow ? "button default-dm success" : "button default-dm secondary";
always_allow_btn.dataset.domain = domain_data.domain;
always_allow_btn.dataset.always_allow = domain_data.always_allow;
always_allow_btn.addEventListener("click", ui.onclick_always_allow_btn)
ui.event_container.innerHTML = "";
domain_data.events.forEach(event => {
const button = document.createElement("button");
button.className = `button event-btn ${event.ignored ? "default-dm warning" : "default-dm danger" }`;
button.innerText = event.id;
button.dataset.id = event.id;
button.dataset.ignored = event.ignored
button.dataset.domain = domain_data.domain;
ui.event_container.appendChild(button);
});
}
async onclick_manual_update_btn() {
ui.set_loading("manual_update_btn", true);
try {
const data = await api.manual_update()
if(data.state==="Starting"){
ui.open_modal(
"Update Triggered",
"Manual update process successfully started."
);
} else {
throw new Error()
}
} catch (error) {
ui.open_modal(
"Update Failed",
"Failed to trigger update process."
);
} finally {
ui.set_loading("manual_update_btn", false);
}
}
async onclick_start_timer_btn() {
ui.set_loading("start_timer_btn", true);
try {
const data = await api.start_timer()
api.update_metrics()
} catch (error) {
console.error(error);
} finally {
ui.set_loading("start_timer_btn", false);
}
}
async onclick_stop_timer_btn() {
ui.set_loading("stop_timer_btn", true);
try {
const data = await api.stop_timer()
api.update_metrics()
} catch (error) {
console.error(error);
} finally {
ui.set_loading("stop_timer_btn", false);
}
}
async onclick_domain_search_btn(){
ui.set_loading("domain_search_btn", true);
try {
const data = await api.domain_search()
ui.render_search_results(data)
} catch (error) {
console.error(error);
} finally {
ui.set_loading("domain_search_btn", false);
}
}
async load_false_positive_controls(){
ui.set_loading("false_positive_load_btn", true);
try {
const data = await api.load_domain_fp()
const parsed_data= {
domain: data.domain,
always_allow: data.always_allowed,
events: []
}
data.events.forEach(event => {
parsed_data.events.push({"id": event, "ignored": data.ignored_events.includes(event)});
})
ui.render_false_positive_controls(parsed_data)
} catch (error) {
console.error(error);
} finally {
ui.set_loading("false_positive_load_btn", false);
}
}
async onclick_false_positive_load_btn(){
await ui.load_false_positive_controls()
}
async onclick_event_button(event){
if (event.target === event.currentTarget) return;
const event_id = event.target.dataset.id;
const domain = event.target.dataset.domain;
const ignored = event.target.dataset.ignored === "true";
try {
if(ignored){
await api.reinstate_event(domain, event_id)
} else {
await api.ignore_event(domain, event_id)
}
await ui.load_false_positive_controls()
} catch (error) {
console.error(error);
}
}
async onclick_always_allow_btn(event){
const domain = event.target.dataset.domain;
const always_allowed = event.target.dataset.always_allow === "true";
try {
await api.change_always_allow(domain, always_allowed)
await ui.load_false_positive_controls()
} catch (error) {
console.error(error);
}
}
onclick_modal_button() {
document.getElementById("modal_backdrop").style.display = "none";
}
}
class API {
constructor() {
const self = this
this.update_metrics()
this.metrics_timer = setInterval(self.update_metrics, 5000)
}
update_metrics() {
axios.get(`misp/dashboard_metrics`)
.then(response => {
ui.update_metrics(response.data);
})
.catch(error => {
console.log(error)
})
}
async manual_update(published_timestamp = null){
const user = await auth.get_user()
return axios.put(
`misp/manual_update`,
{
"published_timestamp": published_timestamp
},
{
headers: {
"Authorization": `Bearer ${user?.access_token}`
}
}
)
.then(response => {
return response.data
})
.catch(error => {
console.log(error)
})
}
async start_timer(){
const user = await auth.get_user()
return axios.put(
`control/start_timer`,
{},
{
headers: {
"Authorization": `Bearer ${user?.access_token}`
}
}
)
.then(response => {
return response.data
})
.catch(error => {
console.log(error)
})
}
async stop_timer(){
const user = await auth.get_user()
return axios.put(
`control/stop_timer`,
{},
{
headers: {
"Authorization": `Bearer ${user?.access_token}`
}
}
)
.then(response => {
return response.data
})
.catch(error => {
console.log(error)
})
}
async domain_search(){
const user = await auth.get_user()
const search_term = document.getElementById("domain_search_input").value
return axios.get(
`misp/domain/search?domain=${search_term}`,
{},
{
headers: {
"Authorization": `Bearer ${user?.access_token}`
}
}
)
.then(response => {
return response.data
})
.catch(error => {
console.log(error)
})
}
async load_domain_fp(){
const user = await auth.get_user()
const search_term = document.getElementById("false_positive_domain").value
return axios.get(
`/misp/domain/details/${search_term}`,
{},
{
headers: {
"Authorization": `Bearer ${user?.access_token}`
}
}
)
.then(response => {
return response.data
})
.catch(error => {
console.log(error)
})
}
async ignore_event(domain, event_id){
const user = await auth.get_user()
return axios.patch(
`/misp/domain/events/${domain}/ignore?event=${event_id}`,
{},
{
headers: {
"Authorization": `Bearer ${user?.access_token}`
}
}
)
.then(response => {
return response.data
})
.catch(error => {
console.log(error)
})
}
async reinstate_event(domain, event_id){
const user = await auth.get_user()
return axios.patch(
`/misp/domain/events/${domain}/reinstate?event=${event_id}`,
{},
{
headers: {
"Authorization": `Bearer ${user?.access_token}`
}
}
)
.then(response => {
return response.data
})
.catch(error => {
console.log(error)
})
}
async change_always_allow(domain, currently_allowed){
const user = await auth.get_user()
return axios.patch(
`/misp/domain/always_allowed/${domain}?allow=${!currently_allowed}`,
{},
{
headers: {
"Authorization": `Bearer ${user?.access_token}`
}
}
)
.then(response => {
return response.data
})
.catch(error => {
console.log(error)
})
}
}
window.onload = () => {
window.ui = new UI()
window.auth = new Auth()
window.api = new API()
window.timer_running = false;
ui.sync_timer_buttons();
}

View file

@ -0,0 +1,120 @@
body {
margin: 0;
padding: 24px;
font-family: Arial, sans-serif;
}
.title {
font-size: 48px;
}
#auth_button {
max-height: 2.5rem;
}
.card {
border: 1px solid #333;
border-radius: 8px;
padding: 18px;
margin-bottom: 20px;
background-color: light-dark(#eee, #171717);
}
.card h2 {
margin-top: 0;
}
.label {
display: block;
margin-bottom: 6px;
font-size: 14px;
}
.input {
width: 100%;
padding: 10px;
border: 1px solid #444;
border-radius: 4px;
box-sizing: border-box;
}
.button {
padding: 10px 14px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
}
.button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.status-good {
color: #4ade80;
font-weight: bold;
}
.status-bad {
color: #f87171;
font-weight: bold;
}
.status-warning {
color: #facc15;
font-weight: bold;
}
.summary-row {
margin-bottom: 12px;
}
.search-results {
margin-top: 12px;
max-height: 400px;
overflow-y: auto;
border: 1px solid #333;
}
.search-result {
padding: 10px;
border-bottom: 1px solid #333;
}
.event-grid {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.event-btn {
min-width: 80px;
}
.top-bar {
margin-bottom: 20px;
}
.modal-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.65);
display: none;
justify-content: center;
align-items: center;
}
.modal {
width: 400px;
background: #1c1c1c;
border: 1px solid #444;
border-radius: 8px;
padding: 24px;
}
.modal-footer {
margin-top: 20px;
text-align: right;
}

105
src/static/index.html Normal file
View file

@ -0,0 +1,105 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Title</title>
<link rel="stylesheet" href="/libraries/luxgrid_combined.min.css"/>
<link rel="stylesheet" href="/assets/index/styles.css"/>
</head>
<body>
<div class="container">
<div class="row top-bar">
<div class="col-2 justify-start">Logo</div>
<div class="col-8 text-center title">Title</div>
<div class="col-2 justify-end">
<button id="auth_button" class="button default-dm success">Logged In</button>
</div>
</div>
<div class="card">
<h2>System Status</h2>
<div class="row">
<div class="col-3 summary-row">
<div class="label">Total Blocked Domains</div>
<div id="blocked_domains"></div>
</div>
<div class="col-3 summary-row">
<div class="label">Timer Status</div>
<div id="timer_status" class="status-warning"></div>
</div>
<div class="col-3 summary-row">
<div class="label">Overall Status</div>
<div id="overall_status" class="status-good"></div>
</div>
<div class="col-3 summary-row">
<div class="label">Last Update</div>
<div id="last_update_complete_time"></div>
<div id="last_update_duration"></div>
</div>
</div>
</div>
<div class="card">
<h2>Actions</h2>
<div class="row">
<div class="col-4">
<button id="manual_update_btn" class="button default-dm primary w-100">Manual Update</button>
</div>
<div class="col-4">
<button id="start_timer_btn" class="button default-dm success w-100" disabled>Start Timer</button>
</div>
<div class="col-4">
<button id="stop_timer_btn" class="button default-dm danger w-100">Stop Timer</button>
</div>
</div>
</div>
<div class="card">
<h2>Domain Search</h2>
<label class="label">Search</label>
<input id="domain_search_input" class="input" type="text" placeholder="partial domain"/>
<div style="margin-top:12px;">
<button id="domain_search_btn" class="button default-dm primary">Search</button>
</div>
<div id="domain_search_results" class="search-results"></div>
</div>
<div class="card">
<h2>False Positive Handling</h2>
<div class="row">
<div class="col-8">
<label class="label">Domain</label>
<input id="false_positive_domain" class="input" type="text" placeholder="domain to manage"/>
</div>
<div class="col-4">
<label class="label">&nbsp;</label>
<button id="false_positive_load_btn" class="button default-dm primary w-100">Load</button>
</div>
</div>
<div id="false_positive_actions" style="display:none; margin-top:20px;">
<div class="row align-center">
<div class="col-4">
<div class="label">Always Allow</div>
</div>
<div class="col-8">
<button id="always_allow_btn" class="button default-dm secondary">Disabled</button>
</div>
</div>
<div style="margin-top:20px;">
<div class="label">Events</div>
<div id="events_container" class="event-grid"></div>
</div>
</div>
</div>
</div>
<div id="modal_backdrop" class="modal-backdrop">
<div class="modal">
<h3 id="modal_title">Status</h3>
<div id="modal_message"></div>
<div class="modal-footer">
<button id="modal_button" class="button default-dm primary">Close</button>
</div>
</div>
</div>
<script src="/libraries/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/oidc-client-ts/3.3.0/browser/oidc-client-ts.min.js" integrity="sha512-c2vT43K5Ap/b44ZVGj+uRqqM/RQFtqludEYk8ztxzAPWcPJaKPSGpJRcbo1c2/PdszmNUvPqOzWERCKqT+P1Xg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="/assets/index/scripts.js" type="module"></script>
</body>
</html>

5
src/static/libraries/axios.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
body,button,input,select,textarea{color:light-dark(black,var(--dm-text-colour))}.col,[class*=col-]{padding-left:15px;padding-right:15px;box-sizing:border-box;min-width:8.33%}:root{color-scheme:light dark;--dm-text-colour:#e0e0e0;--primary-bg:#007bff;--secondary-bg:#6c757d;--success-bg:#28a745;--danger-bg:#dc3545;--warning-bg:#ffc107;--info-bg:#17a2b8;--light-bg:#f8f9fa;--dark-bg:#343a40;--light-bg-text:#212529;--dark-bg-text:white}.danger,.info,.light,.primary,.secondary,.success,.warning{color:var(--light-bg-text)}body{background-color:light-dark(white,#121212)}button{background-color:light-dark(#f0f0f0,#333);border-color:light-dark(white,#555);border-radius:2px}button:hover{background-color:light-dark(#e5e5e5,#555);border-color:light-dark(white,#666)}input,select,textarea{background-color:light-dark(#fafafa,#1e1e1e);border-style:solid;border-width:1px;border-color:light-dark(black,#333)}input:focus,select:focus,textarea:focus{border-color:light-dark(black,#036);border-radius:2px;outline:solid 2px}h1,h2,h3,h4,h5,h6{color:light-dark(black,#fff)}.default{background-color:var(--base-bg-colour)}.default-dm{background-color:light-dark(var(--base-bg-colour),oklch(from var(--base-bg-colour) clamp(0,l + .22,1) clamp(0,c - .02,1) h))}.lighten{background-color:oklch(from var(--base-bg-colour) clamp(0,l + .27,1) c h)}.darken{background-color:oklch(from var(--base-bg-colour) clamp(0,l - .09,1) c h)}.subtle{background-color:rgba(from var(--base-bg-colour) r g b / .6)}.primary{--base-bg-colour:var(--primary-bg)}.secondary{--base-bg-colour:var(--secondary-bg)}.success{--base-bg-colour:var(--success-bg)}.danger{--base-bg-colour:var(--danger-bg)}.warning{--base-bg-colour:var(--warning-bg)}.info{--base-bg-colour:var(--info-bg)}.light{--base-bg-colour:var(--light-bg)}.dark{--base-bg-colour:var(--dark-bg);color:var(--dark-bg-text)}.container{width:100%;margin-left:auto;margin-right:auto;padding-left:15px;padding-right:15px;box-sizing:border-box}.row{display:flex;margin-bottom:5px;flex-wrap:wrap}.col{flex:1;max-width:100%}.col-1{width:8.33%}.col-2{width:16.66%}.col-3,.w-25{width:25%}.col-4{width:33.33%}.col-5{width:41.66%}.col-6,.w-50{width:50%}.col-7{width:58.33%}.col-8{width:66.66%}.col-9,.w-75{width:75%}.col-10{width:83.33%}.col-11{width:91.66%}.col-12,.w-100{width:100%}@media (min-width:576px){.col-sm-1{width:8.33%}.col-sm-2{width:16.66%}.col-sm-3{width:25%}.col-sm-4{width:33.33%}.col-sm-5{width:41.66%}.col-sm-6{width:50%}.col-sm-7{width:58.33%}.col-sm-8{width:66.66%}.col-sm-9{width:75%}.col-sm-10{width:83.33%}.col-sm-11{width:91.66%}.col-sm-12{width:100%}}@media (min-width:768px){.col-md-1{width:8.33%}.col-md-2{width:16.66%}.col-md-3{width:25%}.col-md-4{width:33.33%}.col-md-5{width:41.66%}.col-md-6{width:50%}.col-md-7{width:58.33%}.col-md-8{width:66.66%}.col-md-9{width:75%}.col-md-10{width:83.33%}.col-md-11{width:91.66%}.col-md-12{width:100%}}@media (min-width:992px){.col-lg-1{width:8.33%}.col-lg-2{width:16.66%}.col-lg-3{width:25%}.col-lg-4{width:33.33%}.col-lg-5{width:41.66%}.col-lg-6{width:50%}.col-lg-7{width:58.33%}.col-lg-8{width:66.66%}.col-lg-9{width:75%}.col-lg-10{width:83.33%}.col-lg-11{width:91.66%}.col-lg-12{width:100%}}@media (min-width:1200px){.col-xl-1{width:8.33%}.col-xl-2{width:16.66%}.col-xl-3{width:25%}.col-xl-4{width:33.33%}.col-xl-5{width:41.66%}.col-xl-6{width:50%}.col-xl-7{width:58.33%}.col-xl-8{width:66.66%}.col-xl-9{width:75%}.col-xl-10{width:83.33%}.col-xl-11{width:91.66%}.col-xl-12{width:100%}}.axis-vert{flex-direction:column}.axis-horz{flex-direction:row}.justify-start{display:flex;justify-content:flex-start}.justify-end{display:flex;justify-content:flex-end}.justify-center{display:flex;justify-content:center}.justify-between{display:flex;justify-content:space-between}.justify-around{display:flex;justify-content:space-around}.justify-evenly{display:flex;justify-content:space-evenly}.align-start{display:flex;align-items:flex-start}.align-end{display:flex;align-items:flex-end}.align-center{display:flex;align-items:center}.h-25{height:25%}.h-50{height:50%}.h-75{height:75%}.h-100{height:100%}.vh-25{height:25vh}.vh-50{height:50vh}.vh-75{height:75vh}.vh-100{height:100vh}.vw-25{width:25vw}.vw-50{width:50vw}.vw-75{width:75vw}.vw-100{width:100vw}.text-center{text-align:center}.text-left{text-align:start}.text-right{text-align:end}

View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/oidc-client-ts/3.3.0/browser/oidc-client-ts.min.js"></script>
<script>
const url = "http://localhost:8000"
const settings = {
authority: "https://sso.sr2.uk/realms/sr2",
client_id: "chris-dev",
redirect_uri: url + "/",
response_type: "code",
scope: "openid email profile",
response_mode: "fragment",
}
const user_manager = new oidc.UserManager(settings)
user_manager.signinPopupCallback()
.catch(error => {
console.error(error);
});
</script>
</body>
</html>

View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/oidc-client-ts/3.3.0/browser/oidc-client-ts.min.js"></script>
<script>
const url = "http://localhost:8000"
const settings = {
authority: "https://sso.sr2.uk/realms/sr2",
client_id: "chris-dev",
redirect_uri: url + "/",
response_type: "code",
scope: "openid email profile",
response_mode: "fragment",
}
const user_manager = new oidc.UserManager(settings)
user_manager.signoutPopupCallback()
.catch(error => {
console.error(error);
});
</script>
</body>
</html>

View file

@ -16,3 +16,5 @@ MISP_OUTPUT_FILE=""
ALLOWED_TLP='["tlp:clear", "tlp:white", "tlp:green"]' ALLOWED_TLP='["tlp:clear", "tlp:white", "tlp:green"]'
IGNORED_TLP='["tlp:red", "tlp:amber+strict", "tlp:amber"]' IGNORED_TLP='["tlp:red", "tlp:amber+strict", "tlp:amber"]'
UNBOUND_CERT_DIR="" UNBOUND_CERT_DIR=""
AUTHORISED_USERS='[]'