mirror of
https://github.com/simon987/sist2.git
synced 2025-04-04 07:52:59 +00:00
Add support for auth0
This commit is contained in:
parent
b9f008603a
commit
86ca9f1ecb
@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
project(sist2 C)
|
||||
project(sist2)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
option(SIST_DEBUG "Build a debug executable" on)
|
||||
option(SIST_FAST "Enable more optimisation flags" off)
|
||||
@ -40,9 +40,12 @@ add_executable(sist2
|
||||
src/stats.c src/stats.h src/ctx.c
|
||||
src/parsing/sidecar.c src/parsing/sidecar.h
|
||||
|
||||
src/auth0/auth0_c_api.h src/auth0/auth0_c_api.cpp
|
||||
|
||||
# argparse
|
||||
third-party/argparse/argparse.h third-party/argparse/argparse.c
|
||||
)
|
||||
set_target_properties(sist2 PROPERTIES LINKER_LANGUAGE C)
|
||||
|
||||
target_link_directories(sist2 PRIVATE BEFORE ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/lib/)
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES .a .lib)
|
||||
|
@ -74,6 +74,10 @@ Web options
|
||||
--es-index=<str> Elasticsearch index name. DEFAULT=sist2
|
||||
--bind=<str> Listen on this address. DEFAULT=localhost:4090
|
||||
--auth=<str> Basic auth in user:password format
|
||||
--auth0-audience=<str> API audience/identifier
|
||||
--auth0-domain=<str> Application domain
|
||||
--auth0-client-id=<str> Application client ID
|
||||
--auth0-public-key-file=<str> Path to Auth0 public key file extracted from <domain>/pem
|
||||
--tag-auth=<str> Basic auth in user:password format for tagging
|
||||
--tagline=<str> Tagline in navbar
|
||||
--dev Serve html & js files from disk (for development)
|
||||
@ -268,6 +272,7 @@ sist2 index --print ./my_index/ | jq | less
|
||||
* `--dev` Serve html & js files from disk (for development, used to modify frontend files without having to recompile)
|
||||
* `--lang=<str>` Set the default web UI language (See #180 for a list of supported languages, default
|
||||
is `en`). The user can change the language in the configuration page
|
||||
* `--auth0-audience`, `--auth0-domain`, `--auth0-client-id`, `--auth0-public-key-file` See [Authentication with Auth0](auth0.md)
|
||||
|
||||
### Web examples
|
||||
|
||||
|
19
docs/auth0.md
Normal file
19
docs/auth0.md
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
# Authentication with Auth0
|
||||
|
||||
1. Create a new Auth0 application (Single page app)
|
||||
2. Create a new Auth0 API:
|
||||
1. Choose `RS256` signing algorithm
|
||||
2. Set identifier (audience) to `https://sist2`
|
||||
3. Download the Auth0 certificate from https://<domain>.auth0.com/pem (you can find the domain Applications->Basic information)
|
||||
4. Extract the public key from the certificate using `openssl x509 -pubkey -noout -in cert.pem > pubkey.txt`
|
||||
5. Start the sist2 web server
|
||||
|
||||
Example options:
|
||||
```bash
|
||||
sist2 web \
|
||||
--auth0-client-id XXX \
|
||||
--auth0-audience https://sist2 \
|
||||
--auth0-domain YYY.auth0.com \
|
||||
--auth0-public-key-file /ZZZ/pubkey.txt
|
||||
```
|
2
sist2-admin/frontend/dist/index.html
vendored
2
sist2-admin/frontend/dist/index.html
vendored
@ -1 +1 @@
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>sist2-admin</title><link href="css/app.0f0b676b.css" rel="preload" as="style"><link href="css/chunk-vendors.aa66c7e8.css" rel="preload" as="style"><link href="js/app.b34f501e.js" rel="preload" as="script"><link href="js/chunk-vendors.fad0ee6a.js" rel="preload" as="script"><link href="css/chunk-vendors.aa66c7e8.css" rel="stylesheet"><link href="css/app.0f0b676b.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but sist2-admin-vue doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/chunk-vendors.fad0ee6a.js"></script><script src="js/app.b34f501e.js"></script></body></html>
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>sist2-admin</title><link href="css/app.css" rel="preload" as="style"><link href="css/chunk-vendors.css" rel="preload" as="style"><link href="js/app.js" rel="preload" as="script"><link href="js/chunk-vendors.js" rel="preload" as="script"><link href="css/chunk-vendors.css" rel="stylesheet"><link href="css/app.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but sist2-admin-vue doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/chunk-vendors.js"></script><script src="js/app.js"></script></body></html>
|
File diff suppressed because one or more lines are too long
@ -341,5 +341,4 @@ var e="jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.".split("_"),a=
|
||||
//! moment.js locale configuration
|
||||
var e=t.defineLocale("x-pseudo",{months:"J~áñúá~rý_F~ébrú~árý_~Márc~h_Áp~ríl_~Máý_~Júñé~_Júl~ý_Áú~gúst~_Sép~témb~ér_Ó~ctób~ér_Ñ~óvém~bér_~Décé~mbér".split("_"),monthsShort:"J~áñ_~Féb_~Már_~Ápr_~Máý_~Júñ_~Júl_~Áúg_~Sép_~Óct_~Ñóv_~Déc".split("_"),monthsParseExact:!0,weekdays:"S~úñdá~ý_Mó~ñdáý~_Túé~sdáý~_Wéd~ñésd~áý_T~húrs~dáý_~Fríd~áý_S~átúr~dáý".split("_"),weekdaysShort:"S~úñ_~Móñ_~Túé_~Wéd_~Thú_~Frí_~Sát".split("_"),weekdaysMin:"S~ú_Mó~_Tú_~Wé_T~h_Fr~_Sá".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[T~ódá~ý át] LT",nextDay:"[T~ómó~rró~w át] LT",nextWeek:"dddd [át] LT",lastDay:"[Ý~ést~érdá~ý át] LT",lastWeek:"[L~ást] dddd [át] LT",sameElse:"L"},relativeTime:{future:"í~ñ %s",past:"%s á~gó",s:"á ~féw ~sécó~ñds",ss:"%d s~écóñ~ds",m:"á ~míñ~úté",mm:"%d m~íñú~tés",h:"á~ñ hó~úr",hh:"%d h~óúrs",d:"á ~dáý",dd:"%d d~áýs",M:"á ~móñ~th",MM:"%d m~óñt~hs",y:"á ~ýéár",yy:"%d ý~éárs"},dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10,a=1===~~(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th";return t+a},week:{dow:1,doy:4}});return e}))},fdbf:function(t,e,a){var n=a("4930");t.exports=n&&!Symbol.sham&&"symbol"==typeof Symbol.iterator},ffff:function(t,e,a){(function(t,e){e(a("c1df"))})(0,(function(t){"use strict";
|
||||
//! moment.js locale configuration
|
||||
var e=t.defineLocale("se",{months:"ođđajagemánnu_guovvamánnu_njukčamánnu_cuoŋománnu_miessemánnu_geassemánnu_suoidnemánnu_borgemánnu_čakčamánnu_golggotmánnu_skábmamánnu_juovlamánnu".split("_"),monthsShort:"ođđj_guov_njuk_cuo_mies_geas_suoi_borg_čakč_golg_skáb_juov".split("_"),weekdays:"sotnabeaivi_vuossárga_maŋŋebárga_gaskavahkku_duorastat_bearjadat_lávvardat".split("_"),weekdaysShort:"sotn_vuos_maŋ_gask_duor_bear_láv".split("_"),weekdaysMin:"s_v_m_g_d_b_L".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"MMMM D. [b.] YYYY",LLL:"MMMM D. [b.] YYYY [ti.] HH:mm",LLLL:"dddd, MMMM D. [b.] YYYY [ti.] HH:mm"},calendar:{sameDay:"[otne ti] LT",nextDay:"[ihttin ti] LT",nextWeek:"dddd [ti] LT",lastDay:"[ikte ti] LT",lastWeek:"[ovddit] dddd [ti] LT",sameElse:"L"},relativeTime:{future:"%s geažes",past:"maŋit %s",s:"moadde sekunddat",ss:"%d sekunddat",m:"okta minuhta",mm:"%d minuhtat",h:"okta diimmu",hh:"%d diimmut",d:"okta beaivi",dd:"%d beaivvit",M:"okta mánnu",MM:"%d mánut",y:"okta jahki",yy:"%d jagit"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}});return e}))}}]);
|
||||
//# sourceMappingURL=chunk-vendors.fad0ee6a.js.map
|
||||
var e=t.defineLocale("se",{months:"ođđajagemánnu_guovvamánnu_njukčamánnu_cuoŋománnu_miessemánnu_geassemánnu_suoidnemánnu_borgemánnu_čakčamánnu_golggotmánnu_skábmamánnu_juovlamánnu".split("_"),monthsShort:"ođđj_guov_njuk_cuo_mies_geas_suoi_borg_čakč_golg_skáb_juov".split("_"),weekdays:"sotnabeaivi_vuossárga_maŋŋebárga_gaskavahkku_duorastat_bearjadat_lávvardat".split("_"),weekdaysShort:"sotn_vuos_maŋ_gask_duor_bear_láv".split("_"),weekdaysMin:"s_v_m_g_d_b_L".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"MMMM D. [b.] YYYY",LLL:"MMMM D. [b.] YYYY [ti.] HH:mm",LLLL:"dddd, MMMM D. [b.] YYYY [ti.] HH:mm"},calendar:{sameDay:"[otne ti] LT",nextDay:"[ihttin ti] LT",nextWeek:"dddd [ti] LT",lastDay:"[ikte ti] LT",lastWeek:"[ovddit] dddd [ti] LT",sameElse:"L"},relativeTime:{future:"%s geažes",past:"maŋit %s",s:"moadde sekunddat",ss:"%d sekunddat",m:"okta minuhta",mm:"%d minuhtat",h:"okta diimmu",hh:"%d diimmut",d:"okta beaivi",dd:"%d beaivvit",M:"okta mánnu",MM:"%d mánut",y:"okta jahki",yy:"%d jagit"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}});return e}))}}]);
|
@ -34,6 +34,22 @@
|
||||
<label>{{ $t("webOptions.tagAuth") }}</label>
|
||||
<b-form-input v-model="options.tag_auth" @change="update()"></b-form-input>
|
||||
|
||||
<br>
|
||||
<h5>Auth0 options</h5>
|
||||
<label>{{ $t("webOptions.auth0Audience") }}</label>
|
||||
<b-form-input v-model="options.auth0_audience" @change="update()"></b-form-input>
|
||||
|
||||
<label>{{ $t("webOptions.auth0Domain") }}</label>
|
||||
<b-form-input v-model="options.auth0_domain" @change="update()"></b-form-input>
|
||||
|
||||
<label>{{ $t("webOptions.auth0ClientId") }}</label>
|
||||
<b-form-input v-model="options.auth0_client_id" @change="update()"></b-form-input>
|
||||
|
||||
<label>{{ $t("webOptions.auth0PublicKey") }}</label>
|
||||
<b-textarea rows="10" v-model="options.auth0_public_key" @change="update()"></b-textarea>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -47,6 +47,7 @@ export default {
|
||||
|
||||
selectJobs: "Select jobs",
|
||||
webOptions: {
|
||||
title: "Web options",
|
||||
esUrl: "Elasticsearch URL",
|
||||
esIndex: "Elasticsearch index name",
|
||||
esInsecure: "Do not verify SSL connections to Elasticsearch.",
|
||||
|
@ -48,7 +48,7 @@
|
||||
|
||||
<br/>
|
||||
|
||||
<h4>{{ $t("jobOptions.title") }}</h4>
|
||||
<h4>{{ $t("webOptions.title") }}</h4>
|
||||
<b-card>
|
||||
<WebOptions :options="frontend.web_options" :frontend-name="$route.params.name" @change="update()"></WebOptions>
|
||||
</b-card>
|
||||
|
@ -1,3 +1,5 @@
|
||||
module.exports = {
|
||||
publicPath: ""
|
||||
publicPath: "",
|
||||
filenameHashing: false,
|
||||
productionSourceMap: false,
|
||||
};
|
@ -37,6 +37,11 @@ class WebOptions(BaseModel):
|
||||
tagline: str = "Lightning-fast file system indexer and search tool"
|
||||
dev: bool = False
|
||||
lang: str = "en"
|
||||
auth0_audience: str = None
|
||||
auth0_domain: str = None
|
||||
auth0_client_id: str = None
|
||||
auth0_public_key: str = None
|
||||
auth0_public_key_file: str = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
@ -45,6 +50,14 @@ class WebOptions(BaseModel):
|
||||
args = ["web", f"--es-url={self.es_url}", f"--bind={self.bind}",
|
||||
f"--tagline={self.tagline}", f"--lang={self.lang}"]
|
||||
|
||||
if self.auth0_audience:
|
||||
args.append(f"--auth0-audience={self.auth0_audience}")
|
||||
if self.auth0_domain:
|
||||
args.append(f"--auth0-domain={self.auth0_domain}")
|
||||
if self.auth0_client_id:
|
||||
args.append(f"--auth0-client-id={self.auth0_client_id}")
|
||||
if self.auth0_public_key_file:
|
||||
args.append(f"--auth0-public-key-file={self.auth0_public_key_file}")
|
||||
if self.es_insecure_ssl:
|
||||
args.append(f"--es-insecure-ssl")
|
||||
if self.auth:
|
||||
@ -283,6 +296,14 @@ class Sist2:
|
||||
# pipe_wrapper.close()
|
||||
|
||||
def web(self, options: WebOptions, name: str):
|
||||
|
||||
if options.auth0_public_key:
|
||||
with NamedTemporaryFile("w", prefix="sist2-admin", suffix=".txt", delete=False) as f:
|
||||
f.write(options.auth0_public_key)
|
||||
options.auth0_public_key_file = f.name
|
||||
else:
|
||||
options.auth0_public_key_file = None
|
||||
|
||||
args = [
|
||||
self._bin_path,
|
||||
*options.args()
|
||||
|
8
sist2-vue/dist/css/chunk-vendors.css
vendored
Normal file
8
sist2-vue/dist/css/chunk-vendors.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
sist2-vue/dist/css/index.css
vendored
Normal file
1
sist2-vue/dist/css/index.css
vendored
Normal file
File diff suppressed because one or more lines are too long
33
sist2-vue/dist/index.html
vendored
33
sist2-vue/dist/index.html
vendored
@ -1,32 +1,3 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'/>
|
||||
|
||||
<title>sist2</title>
|
||||
<link href="js/chunk-vendors.js" rel="preload" as="script"><link href="js/index.js" rel="preload" as="script"></head>
|
||||
<body>
|
||||
<noscript>
|
||||
<style>
|
||||
body {
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"/><title>sist2</title><script defer="defer" src="js/chunk-vendors.js"></script><script defer="defer" src="js/index.js"></script><link href="css/chunk-vendors.css" rel="stylesheet"><link href="css/index.css" rel="stylesheet"></head><body><noscript><style>body {
|
||||
height: initial;
|
||||
}
|
||||
</style>
|
||||
<div style="text-align: center; margin-top: 100px">
|
||||
<strong>
|
||||
We're sorry but sist2 doesn't work properly without JavaScript enabled.
|
||||
Please enable it to continue.
|
||||
</strong>
|
||||
<br/>
|
||||
<strong>
|
||||
Nous sommes désolés mais sist2 ne fonctionne pas correctement
|
||||
si JavaScript est activé.
|
||||
Veuillez l'activer pour continuer.
|
||||
</strong>
|
||||
</div>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<script type="text/javascript" src="js/chunk-vendors.js"></script><script type="text/javascript" src="js/index.js"></script></body>
|
||||
</html>
|
||||
}</style><div style="text-align: center; margin-top: 100px"><strong>We're sorry but sist2 doesn't work properly without JavaScript enabled. Please enable it to continue.</strong><br/><strong>Nous sommes désolés mais sist2 ne fonctionne pas correctement si JavaScript est activé. Veuillez l'activer pour continuer.</strong></div></noscript><div id="app"></div></body></html>
|
19501
sist2-vue/dist/js/chunk-vendors.js
vendored
19501
sist2-vue/dist/js/chunk-vendors.js
vendored
File diff suppressed because one or more lines are too long
4102
sist2-vue/dist/js/index.js
vendored
4102
sist2-vue/dist/js/index.js
vendored
File diff suppressed because one or more lines are too long
23299
sist2-vue/package-lock.json
generated
23299
sist2-vue/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -7,14 +7,15 @@
|
||||
"build": "vue-cli-service build --mode production"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth0/auth0-spa-js": "^2.0.2",
|
||||
"@egjs/vue-infinitegrid": "3.3.0",
|
||||
"axios": "^0.25.0",
|
||||
"bootstrap-vue": "^2.21.2",
|
||||
"core-js": "^3.6.5",
|
||||
"d3": "^7.6.1",
|
||||
"d3": "^5.6.1",
|
||||
"date-fns": "^2.21.3",
|
||||
"dom-to-image": "^2.6.0",
|
||||
"fslightbox-vue": "file:../../../mnt/Hatchery/main/projects/sist2/fslightbox-vue-pro-1.3.1.tgz",
|
||||
"fslightbox-vue": "file:../../../../mnt/Hatchery/projects/sist2/fslightbox-vue-pro-1.3.1.tgz",
|
||||
"nouislider": "^15.2.0",
|
||||
"underscore": "^1.13.1",
|
||||
"vue": "^2.6.12",
|
||||
@ -26,12 +27,12 @@
|
||||
"vuex": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/polyfill": "^7.11.5",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||
"@vue/cli-plugin-vuex": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@vue/cli-plugin-babel": "~5.0.8",
|
||||
"@vue/cli-plugin-router": "~5.0.8",
|
||||
"@vue/cli-plugin-typescript": "^5.0.8",
|
||||
"@vue/cli-plugin-vuex": "~5.0.8",
|
||||
"@vue/cli-service": "^5.0.8",
|
||||
"@vue/test-utils": "^1.0.3",
|
||||
"bootstrap": "^4.5.2",
|
||||
"inspire-tree": "^4.3.1",
|
||||
|
@ -1,19 +1,33 @@
|
||||
<template>
|
||||
<div id="app" :class="getClass()">
|
||||
<div id="app" :class="getClass()" v-if="!authLoading">
|
||||
<NavBar></NavBar>
|
||||
<router-view v-if="!configLoading"/>
|
||||
</div>
|
||||
<div class="loading-page" v-else>
|
||||
<div class="loading-spinners">
|
||||
<b-spinner type="grow" variant="primary"></b-spinner>
|
||||
<b-spinner type="grow" variant="primary"></b-spinner>
|
||||
<b-spinner type="grow" variant="primary"></b-spinner>
|
||||
</div>
|
||||
<div class="loading-text">
|
||||
Loading • Chargement • 装载
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NavBar from "@/components/NavBar";
|
||||
import {mapGetters} from "vuex";
|
||||
import {mapActions, mapGetters, mapMutations} from "vuex";
|
||||
import Sist2Api from "@/Sist2Api";
|
||||
import {setupAuth0} from "@/main";
|
||||
|
||||
export default {
|
||||
components: {NavBar},
|
||||
data() {
|
||||
return {
|
||||
configLoading: false
|
||||
configLoading: false,
|
||||
authLoading: true,
|
||||
sist2InfoLoading: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -30,9 +44,43 @@ export default {
|
||||
this.configLoading = true;
|
||||
window.setTimeout(() => this.configLoading = false, 10);
|
||||
}
|
||||
|
||||
if (mutation.type === "setAuth0Token") {
|
||||
this.authLoading = false;
|
||||
}
|
||||
});
|
||||
|
||||
Sist2Api.getSist2Info().then(data => {
|
||||
|
||||
if (data.auth0Enabled) {
|
||||
this.authLoading = true;
|
||||
setupAuth0(data.auth0Domain, data.auth0ClientId, data.auth0Audience)
|
||||
|
||||
this.$auth.$watch("loading", loading => {
|
||||
if (loading === false) {
|
||||
|
||||
if (!this.$auth.isAuthenticated) {
|
||||
this.$auth.loginWithRedirect();
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove "code" param
|
||||
window.history.replaceState({}, "", "/" + window.location.hash);
|
||||
|
||||
this.$store.dispatch("loadAuth0Token");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.authLoading = false;
|
||||
}
|
||||
|
||||
this.setSist2Info(data);
|
||||
this.setIndices(data.indices)
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
...mapActions(["setSist2Info",]),
|
||||
...mapMutations(["setIndices",]),
|
||||
getClass() {
|
||||
return {
|
||||
"theme-light": this.optTheme === "light",
|
||||
@ -314,4 +362,22 @@ mark {
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.loading-page {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
gap: 15px
|
||||
}
|
||||
|
||||
.loading-spinners {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
@ -9,13 +9,15 @@
|
||||
|
||||
<span class="badge badge-pill version" v-if="$store && $store.state.sist2Info">
|
||||
v{{ sist2Version() }}<span v-if="isDebug()">-dbg</span><span v-if="isLegacy() && !hideLegacy()">-<a
|
||||
href="https://github.com/simon987/sist2/blob/master/docs/USAGE.md#elasticsearch" target="_blank">legacyES</a></span>
|
||||
href="https://github.com/simon987/sist2/blob/master/docs/USAGE.md#elasticsearch"
|
||||
target="_blank">legacyES</a></span>
|
||||
</span>
|
||||
|
||||
<span v-if="$store && $store.state.sist2Info" class="tagline" v-html="tagline()"></span>
|
||||
|
||||
<b-button class="ml-auto" to="stats" variant="link">{{ $t("stats") }}</b-button>
|
||||
<b-button to="config" variant="link">{{ $t("config") }}</b-button>
|
||||
<b-button v-if="$auth && $auth.isAuthenticated" variant="link" @click="onLogoutClick()">logout</b-button>
|
||||
</b-navbar>
|
||||
</template>
|
||||
|
||||
@ -40,6 +42,9 @@ export default {
|
||||
},
|
||||
hideLegacy() {
|
||||
return this.$store.state.optHideLegacy;
|
||||
},
|
||||
onLogoutClick() {
|
||||
this.$auth.logout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -335,8 +335,8 @@ export default {
|
||||
indexPicker: {
|
||||
selectNone: "Sélectionner aucun",
|
||||
selectAll: "Sélectionner tout",
|
||||
selectedIndex: "indice sélectionné",
|
||||
selectedIndices: "indices sélectionnés",
|
||||
selectedIndex: "index sélectionné",
|
||||
selectedIndices: "index sélectionnés",
|
||||
},
|
||||
},
|
||||
"zh-CN": {
|
||||
|
@ -3,16 +3,32 @@ import 'mutationobserver-shim'
|
||||
import Vue from 'vue'
|
||||
import './plugins/bootstrap-vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import router, {setUseAuth0} from './router'
|
||||
import store from './store'
|
||||
import VueI18n from "vue-i18n";
|
||||
import messages from "@/i18n/messages";
|
||||
|
||||
import { Auth0Plugin } from './plugins/auth0';
|
||||
|
||||
import VueRouter from "vue-router";
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
export function setupAuth0(domain, clientId, audience) {
|
||||
|
||||
setUseAuth0(true);
|
||||
|
||||
Vue.use(Auth0Plugin, {
|
||||
domain,
|
||||
clientId,
|
||||
audience,
|
||||
onRedirectCallback: appState => {}
|
||||
});
|
||||
}
|
||||
|
||||
Vue.prototype.$auth = null;
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
Vue.use(VueI18n);
|
||||
Vue.use(VueRouter);
|
||||
|
||||
|
138
sist2-vue/src/plugins/auth0.js
Normal file
138
sist2-vue/src/plugins/auth0.js
Normal file
@ -0,0 +1,138 @@
|
||||
import Vue from 'vue';
|
||||
import {createAuth0Client} from '@auth0/auth0-spa-js';
|
||||
|
||||
/** Define a default action to perform after authentication */
|
||||
const DEFAULT_REDIRECT_CALLBACK = () =>
|
||||
window.history.replaceState({}, document.title, window.location.pathname);
|
||||
|
||||
let instance;
|
||||
|
||||
/** Returns the current instance of the SDK */
|
||||
export const getInstance = () => instance;
|
||||
|
||||
/** Creates an instance of the Auth0 SDK. If one has already been created, it returns that instance */
|
||||
export const useAuth0 = ({
|
||||
domain, clientId, audience,
|
||||
onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
|
||||
redirectUri = window.location.origin,
|
||||
}) => {
|
||||
if (instance) return instance;
|
||||
|
||||
// The 'instance' is simply a Vue object
|
||||
instance = new Vue({
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
isAuthenticated: false,
|
||||
user: {},
|
||||
auth0Client: null,
|
||||
popupOpen: false,
|
||||
error: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
/** Authenticates the user using a popup window */
|
||||
async loginWithPopup(options, config) {
|
||||
this.popupOpen = true;
|
||||
|
||||
try {
|
||||
await this.auth0Client.loginWithPopup(options, config);
|
||||
this.user = await this.auth0Client.getUser();
|
||||
this.isAuthenticated = await this.auth0Client.isAuthenticated();
|
||||
this.error = null;
|
||||
} catch (e) {
|
||||
this.error = e;
|
||||
// eslint-disable-next-line
|
||||
console.error(e);
|
||||
} finally {
|
||||
this.popupOpen = false;
|
||||
}
|
||||
|
||||
this.user = await this.auth0Client.getUser();
|
||||
this.isAuthenticated = true;
|
||||
},
|
||||
/** Handles the callback when logging in using a redirect */
|
||||
async handleRedirectCallback() {
|
||||
this.loading = true;
|
||||
try {
|
||||
await this.auth0Client.handleRedirectCallback();
|
||||
this.user = await this.auth0Client.getUser();
|
||||
this.isAuthenticated = true;
|
||||
this.error = null;
|
||||
} catch (e) {
|
||||
this.error = e;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
/** Authenticates the user using the redirect method */
|
||||
loginWithRedirect(o) {
|
||||
return this.auth0Client.loginWithRedirect(o);
|
||||
},
|
||||
/** Returns all the claims present in the ID token */
|
||||
getIdTokenClaims(o) {
|
||||
return this.auth0Client.getIdTokenClaims(o);
|
||||
},
|
||||
/** Returns the access token. If the token is invalid or missing, a new one is retrieved */
|
||||
getTokenSilently(o) {
|
||||
return this.auth0Client.getTokenSilently(o);
|
||||
},
|
||||
|
||||
/** Gets the access token using a popup window */
|
||||
|
||||
getTokenWithPopup(o) {
|
||||
return this.auth0Client.getTokenWithPopup(o);
|
||||
},
|
||||
/** Logs the user out and removes their session on the authorization server */
|
||||
logout() {
|
||||
return this.auth0Client.logout({ logoutParams: { returnTo: window.location.origin } });
|
||||
}
|
||||
},
|
||||
/** Use this lifecycle method to instantiate the SDK client */
|
||||
async created() {
|
||||
// Create a new instance of the SDK client using members of the given options object
|
||||
this.auth0Client = await createAuth0Client({
|
||||
domain: domain,
|
||||
clientId: clientId,
|
||||
|
||||
authorizationParams: {
|
||||
redirect_uri: redirectUri,
|
||||
audience: audience,
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// If the user is returning to the app after authentication..
|
||||
if (
|
||||
window.location.search.includes('code=') &&
|
||||
window.location.search.includes('state=')
|
||||
) {
|
||||
// handle the redirect and retrieve tokens
|
||||
const {appState} = await this.auth0Client.handleRedirectCallback();
|
||||
|
||||
this.error = null;
|
||||
|
||||
// Notify subscribers that the redirect callback has happened, passing the appState
|
||||
// (useful for retrieving any pre-authentication state)
|
||||
onRedirectCallback(appState);
|
||||
}
|
||||
} catch (e) {
|
||||
this.error = e;
|
||||
} finally {
|
||||
// Initialize our internal authentication state
|
||||
this.isAuthenticated = await this.auth0Client.isAuthenticated();
|
||||
this.user = await this.auth0Client.getUser();
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
// Create a simple Vue plugin to expose the wrapper object throughout the application
|
||||
export const Auth0Plugin = {
|
||||
install(Vue, options) {
|
||||
Vue.prototype.$auth = useAuth0(options);
|
||||
}
|
||||
};
|
28
sist2-vue/src/router/auth0.ts
Normal file
28
sist2-vue/src/router/auth0.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import {getInstance} from "@/plugins/auth0";
|
||||
|
||||
export const authGuard = (to, from, next) => {
|
||||
|
||||
const authService = getInstance();
|
||||
|
||||
const fn = () => {
|
||||
// If the user is authenticated, continue with the route
|
||||
if (authService.isAuthenticated) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Otherwise, log in
|
||||
authService.loginWithRedirect({appState: {targetUrl: to.fullPath}});
|
||||
};
|
||||
|
||||
// If loading has already finished, check our auth state using `fn()`
|
||||
if (!authService.loading) {
|
||||
return fn();
|
||||
}
|
||||
|
||||
// Watch for the loading property to change before we check isAuthenticated
|
||||
authService.$watch("loading", loading => {
|
||||
if (loading === false) {
|
||||
return fn();
|
||||
}
|
||||
});
|
||||
};
|
@ -4,14 +4,29 @@ import StatsPage from "../views/StatsPage.vue"
|
||||
import Configuration from "../views/Configuration.vue"
|
||||
import SearchPage from "@/views/SearchPage.vue";
|
||||
import FilePage from "@/views/FilePage.vue";
|
||||
import {authGuard as auth0AuthGuard} from "@/router/auth0";
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
let USE_AUTH0 = false
|
||||
export function setUseAuth0(val) {
|
||||
USE_AUTH0 = val;
|
||||
}
|
||||
|
||||
const authGuard = (to, from, next) => {
|
||||
if (USE_AUTH0) {
|
||||
return auth0AuthGuard(to, from, next);
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
const routes: Array<RouteConfig> = [
|
||||
{
|
||||
path: "/",
|
||||
name: "SearchPage",
|
||||
component: SearchPage
|
||||
component: SearchPage,
|
||||
beforeEnter: authGuard
|
||||
},
|
||||
{
|
||||
path: "/stats",
|
||||
@ -34,7 +49,7 @@ const router = new VueRouter({
|
||||
mode: "hash",
|
||||
base: process.env.BASE_URL,
|
||||
routes,
|
||||
scrollBehavior (to, from, savedPosition) {
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
// return desired position
|
||||
}
|
||||
})
|
||||
|
@ -3,6 +3,7 @@ import Vuex from "vuex"
|
||||
import VueRouter, {Route} from "vue-router";
|
||||
import {EsHit, EsResult, EsTag, Index, Tag} from "@/Sist2Api";
|
||||
import {deserializeMimes, randomSeed, serializeMimes} from "@/util";
|
||||
import {getInstance} from "@/plugins/auth0.js";
|
||||
|
||||
const CONF_VERSION = 2;
|
||||
|
||||
@ -82,7 +83,9 @@ export default new Vuex.Store({
|
||||
uiDetailsMimeAgg: null,
|
||||
uiShowDetails: false,
|
||||
|
||||
uiMimeMap: [] as any[]
|
||||
uiMimeMap: [] as any[],
|
||||
|
||||
auth0Token: null
|
||||
},
|
||||
mutations: {
|
||||
setUiShowDetails: (state, val) => state.uiShowDetails = val,
|
||||
@ -188,6 +191,7 @@ export default new Vuex.Store({
|
||||
busTnTouchStart: (doc_id) => {
|
||||
// noop
|
||||
},
|
||||
setAuth0Token: (state, val) => state.auth0Token = val,
|
||||
},
|
||||
actions: {
|
||||
setSist2Info: (store, val) => {
|
||||
@ -332,6 +336,14 @@ export default new Vuex.Store({
|
||||
commit("setUiLightboxCaptions", []);
|
||||
commit("setUiLightboxKey", 0);
|
||||
commit("setUiDetailsMimeAgg", null);
|
||||
},
|
||||
async loadAuth0Token({commit}) {
|
||||
const authService = getInstance();
|
||||
|
||||
const accessToken = await authService.getTokenSilently()
|
||||
commit("setAuth0Token", accessToken);
|
||||
|
||||
document.cookie = `sist2-auth0=${accessToken};`;
|
||||
}
|
||||
},
|
||||
modules: {},
|
||||
|
@ -153,7 +153,7 @@ export default {
|
||||
components: {LanguageIcon, GearIcon, DebugInfo, Preloader},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
loading: false,
|
||||
configLoading: false,
|
||||
langOptions: [
|
||||
{value: "en", text: this.$t("lang.en")},
|
||||
@ -257,11 +257,6 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
sist2.getSist2Info().then(data => {
|
||||
this.setSist2Info(data);
|
||||
this.loading = false;
|
||||
});
|
||||
|
||||
this.$store.subscribe((mutation) => {
|
||||
if (mutation.type.startsWith("setOpt")) {
|
||||
this.$store.dispatch("updateConfiguration");
|
||||
|
@ -107,13 +107,6 @@ export default Vue.extend({
|
||||
|
||||
},
|
||||
mounted() {
|
||||
if (this.$store.state.sist2Info === null) {
|
||||
sist2.getSist2Info().then(data => {
|
||||
this.$store.dispatch("setSist2Info", data);
|
||||
this.$store.commit("setIndices", data.indices);
|
||||
});
|
||||
}
|
||||
|
||||
let query = null;
|
||||
if (this.$route.query.byId) {
|
||||
query = this.findById(this.$route.query.byId);
|
||||
|
@ -130,25 +130,18 @@ export default Vue.extend({
|
||||
});
|
||||
});
|
||||
|
||||
this.setIndices(this.$store.getters["sist2Info"].indices)
|
||||
|
||||
this.getDateRange().then((range: { min: number, max: number }) => {
|
||||
this.setDateBoundsMin(range.min);
|
||||
this.setDateBoundsMax(range.max);
|
||||
|
||||
sist2.getSist2Info().then(data => {
|
||||
this.setSist2Info(data);
|
||||
this.setIndices(data.indices);
|
||||
const doBlankSearch = !this.$store.state.optUpdateMimeMap;
|
||||
|
||||
const doBlankSearch = !this.$store.state.optUpdateMimeMap;
|
||||
|
||||
Sist2Api.getMimeTypes(Sist2Query.searchQuery(doBlankSearch)).then(({mimeMap}) => {
|
||||
this.$store.commit("setUiMimeMap", mimeMap);
|
||||
this.uiLoading = false;
|
||||
this.search(true);
|
||||
});
|
||||
|
||||
}).catch(() => {
|
||||
this.showErrorToast();
|
||||
Sist2Api.getMimeTypes(Sist2Query.searchQuery(doBlankSearch)).then(({mimeMap}) => {
|
||||
this.$store.commit("setUiMimeMap", mimeMap);
|
||||
this.uiLoading = false;
|
||||
this.search(true);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
48
src/auth0/auth0_c_api.cpp
Normal file
48
src/auth0/auth0_c_api.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
#include "auth0_c_api.h"
|
||||
#include "jwt/jwt.hpp"
|
||||
#include "iostream"
|
||||
#include "cjson/cJSON.h"
|
||||
|
||||
int auth0_verify_jwt(const char *secret_str, const char *token, const char *audience) {
|
||||
|
||||
using namespace jwt::params;
|
||||
|
||||
jwt::jwt_object object;
|
||||
try {
|
||||
object = jwt::decode(
|
||||
token,
|
||||
algorithms({"RS256"}),
|
||||
secret(secret_str),
|
||||
verify(true)
|
||||
);
|
||||
|
||||
} catch (const jwt::TokenExpiredError& e) {
|
||||
return AUTH0_ERR_EXPIRED;
|
||||
} catch (const jwt::SignatureFormatError& e) {
|
||||
return AUTH0_ERR_SIG_FORMAT;
|
||||
} catch (const jwt::DecodeError& e) {
|
||||
return AUTH0_ERR_DECODE;
|
||||
} catch (const jwt::VerificationError& e) {
|
||||
return AUTH0_ERR_VERIFICATION;
|
||||
}
|
||||
|
||||
std::stringstream buf;
|
||||
buf << object.payload();
|
||||
std::string json_payload_str = buf.str();
|
||||
cJSON *payload = cJSON_Parse(json_payload_str.c_str());
|
||||
|
||||
bool audience_ok = false;
|
||||
cJSON *aud;
|
||||
cJSON_ArrayForEach(aud, cJSON_GetObjectItem(payload, "aud")) {
|
||||
if (aud != nullptr && strcmp(aud->valuestring, audience) == 0) {
|
||||
audience_ok = true;
|
||||
}
|
||||
}
|
||||
cJSON_Delete(payload);
|
||||
|
||||
if (!audience_ok) {
|
||||
return AUTH0_ERR_AUDIENCE;
|
||||
}
|
||||
|
||||
return AUTH0_OK;
|
||||
}
|
21
src/auth0/auth0_c_api.h
Normal file
21
src/auth0/auth0_c_api.h
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef SIST2_AUTH0_C_API_H
|
||||
#define SIST2_AUTH0_C_API_H
|
||||
|
||||
#include "stdlib.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define EXTERNC extern "C"
|
||||
#else
|
||||
#define EXTERNC
|
||||
#endif
|
||||
|
||||
#define AUTH0_OK (0)
|
||||
#define AUTH0_ERR_EXPIRED (1)
|
||||
#define AUTH0_ERR_SIG_FORMAT (2)
|
||||
#define AUTH0_ERR_DECODE (3)
|
||||
#define AUTH0_ERR_VERIFICATION (4)
|
||||
#define AUTH0_ERR_AUDIENCE (5)
|
||||
|
||||
EXTERNC int auth0_verify_jwt(const char *secret, const char *token, const char* audience);
|
||||
|
||||
#endif
|
31
src/cli.c
31
src/cli.c
@ -501,6 +501,37 @@ int web_args_validate(web_args_t *args, int argc, const char **argv) {
|
||||
args->tag_auth_enabled = FALSE;
|
||||
}
|
||||
|
||||
if (args->auth0_public_key_path != NULL || args->auth0_audience != NULL || args->auth0_client_id ||
|
||||
args->auth0_domain) {
|
||||
|
||||
if (args->auth0_public_key_path == NULL) {
|
||||
fprintf(stderr, "Missing --auth0-public-key-file argument");
|
||||
return 1;
|
||||
}
|
||||
if (args->auth0_audience == NULL) {
|
||||
fprintf(stderr, "Missing --auth0-audience argument");
|
||||
return 1;
|
||||
}
|
||||
if (args->auth0_client_id == NULL) {
|
||||
fprintf(stderr, "Missing --auth0-client-id argument");
|
||||
return 1;
|
||||
}
|
||||
if (args->auth0_domain == NULL) {
|
||||
fprintf(stderr, "Missing --auth0-domain argument");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (args->auth0_public_key_path != NULL) {
|
||||
if (load_external_file(args->auth0_public_key_path, &args->auth0_public_key) != 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
args->auth0_enabled = TRUE;
|
||||
} else {
|
||||
args->auth0_enabled = FALSE;
|
||||
}
|
||||
|
||||
args->index_count = argc - 1;
|
||||
args->indices = argv + 1;
|
||||
|
||||
|
@ -77,6 +77,12 @@ typedef struct web_args {
|
||||
char *lang;
|
||||
char auth_user[256];
|
||||
char auth_pass[256];
|
||||
int auth0_enabled;
|
||||
char *auth0_audience;
|
||||
char *auth0_domain;
|
||||
char *auth0_client_id;
|
||||
char *auth0_public_key_path;
|
||||
char *auth0_public_key;
|
||||
int auth_enabled;
|
||||
int tag_auth_enabled;
|
||||
int index_count;
|
||||
|
@ -105,6 +105,13 @@ typedef struct {
|
||||
char *auth_pass;
|
||||
int auth_enabled;
|
||||
int tag_auth_enabled;
|
||||
|
||||
int auth0_enabled;
|
||||
char *auth0_public_key;
|
||||
char *auth0_audience;
|
||||
char *auth0_domain;
|
||||
char *auth0_client_id;
|
||||
|
||||
char *tagline;
|
||||
struct index_t indices[256];
|
||||
char lang[10];
|
||||
|
10
src/main.c
10
src/main.c
@ -13,6 +13,7 @@
|
||||
#include "web/serve.h"
|
||||
#include "parsing/mime.h"
|
||||
#include "parsing/parse.h"
|
||||
#include "auth0/auth0_c_api.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
@ -562,6 +563,11 @@ void sist2_web(web_args_t *args) {
|
||||
WebCtx.tag_auth_enabled = args->tag_auth_enabled;
|
||||
WebCtx.tagline = args->tagline;
|
||||
WebCtx.dev = args->dev;
|
||||
WebCtx.auth0_enabled = args->auth0_enabled;
|
||||
WebCtx.auth0_public_key = args->auth0_public_key;
|
||||
WebCtx.auth0_client_id = args->auth0_client_id;
|
||||
WebCtx.auth0_domain = args->auth0_domain;
|
||||
WebCtx.auth0_audience = args->auth0_audience;
|
||||
strcpy(WebCtx.lang, args->lang);
|
||||
|
||||
for (int i = 0; i < args->index_count; i++) {
|
||||
@ -711,6 +717,10 @@ int main(int argc, const char *argv[]) {
|
||||
OPT_STRING(0, "es-index", &common_es_index, "Elasticsearch index name. DEFAULT=sist2"),
|
||||
OPT_STRING(0, "bind", &web_args->listen_address, "Listen on this address. DEFAULT=localhost:4090"),
|
||||
OPT_STRING(0, "auth", &web_args->credentials, "Basic auth in user:password format"),
|
||||
OPT_STRING(0, "auth0-audience", &web_args->auth0_audience, "API audience/identifier"),
|
||||
OPT_STRING(0, "auth0-domain", &web_args->auth0_domain, "Application domain"),
|
||||
OPT_STRING(0, "auth0-client-id", &web_args->auth0_client_id, "Application client ID"),
|
||||
OPT_STRING(0, "auth0-public-key-file", &web_args->auth0_public_key_path, "Path to Auth0 public key file extracted from <domain>/pem"),
|
||||
OPT_STRING(0, "tag-auth", &web_args->tag_credentials, "Basic auth in user:password format for tagging"),
|
||||
OPT_STRING(0, "tagline", &web_args->tagline, "Tagline in navbar"),
|
||||
OPT_BOOLEAN(0, "dev", &web_args->dev, "Serve html & js files from disk (for development)"),
|
||||
|
@ -49,7 +49,7 @@
|
||||
#include <ctype.h>
|
||||
#include "git_hash.h"
|
||||
|
||||
#define VERSION "2.13.0"
|
||||
#define VERSION "2.13.1"
|
||||
static const char *const Version = VERSION;
|
||||
|
||||
#ifndef SIST_PLATFORM
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "static_generated.c"
|
||||
#include "src/index/elastic.h"
|
||||
#include "src/index/web.h"
|
||||
#include "src/auth0/auth0_c_api.h"
|
||||
|
||||
#include <src/ctx.h>
|
||||
|
||||
@ -342,6 +343,14 @@ void index_info(struct mg_connection *nc) {
|
||||
cJSON_AddStringToObject(json, "sist2Hash", Sist2CommitHash);
|
||||
cJSON_AddStringToObject(json, "lang", WebCtx.lang);
|
||||
cJSON_AddBoolToObject(json, "dev", WebCtx.dev);
|
||||
|
||||
cJSON_AddBoolToObject(json, "auth0Enabled", WebCtx.auth0_enabled);
|
||||
if (WebCtx.auth0_enabled) {
|
||||
cJSON_AddStringToObject(json, "auth0Domain", WebCtx.auth0_domain);
|
||||
cJSON_AddStringToObject(json, "auth0ClientId", WebCtx.auth0_client_id);
|
||||
cJSON_AddStringToObject(json, "auth0Audience", WebCtx.auth0_audience);
|
||||
}
|
||||
|
||||
#ifdef SIST_DEBUG
|
||||
cJSON_AddBoolToObject(json, "debug", TRUE);
|
||||
#else
|
||||
@ -588,6 +597,42 @@ int validate_auth(struct mg_connection *nc, struct mg_http_message *hm) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int check_auth0(struct mg_http_message *hm) {
|
||||
|
||||
struct mg_str *cookie = mg_http_get_header(hm, "Cookie");
|
||||
if (cookie == NULL) {
|
||||
LOG_WARNING("serve.c", "Unauthorized request (no auth cookie)")
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
struct mg_str token = mg_str("");
|
||||
char *token_str = NULL;
|
||||
|
||||
token = mg_http_get_header_var(*cookie, mg_str("sist2-auth0"));
|
||||
if (token.len == 0) {
|
||||
LOG_WARNING("serve.c", "Unauthorized request (no auth cookie)")
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
token_str = malloc(token.len + 1);
|
||||
strncpy(token_str, token.ptr, token.len);
|
||||
*(token_str + token.len) = '\0';
|
||||
|
||||
int res = auth0_verify_jwt(
|
||||
WebCtx.auth0_public_key,
|
||||
token_str,
|
||||
WebCtx.auth0_audience
|
||||
);
|
||||
free(token_str);
|
||||
|
||||
if (res != AUTH0_OK) {
|
||||
LOG_WARNINGF("serve.c", "Unauthorized request (JWT validation error: %d)", res);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void ev_router(struct mg_connection *nc, int ev, void *ev_data, UNUSED(void *fn_data)) {
|
||||
|
||||
if (ev == MG_EV_HTTP_MSG) {
|
||||
@ -606,20 +651,34 @@ static void ev_router(struct mg_connection *nc, int ev, void *ev_data, UNUSED(vo
|
||||
|
||||
if (mg_http_match_uri(hm, "/")) {
|
||||
search_index(nc, hm);
|
||||
return;
|
||||
} else if (mg_http_match_uri(hm, "/favicon.ico")) {
|
||||
favicon(nc, hm);
|
||||
return;
|
||||
} else if (mg_http_match_uri(hm, "/css/index.css")) {
|
||||
style(nc, hm);
|
||||
return;
|
||||
} else if (mg_http_match_uri(hm, "/css/chunk-vendors.css")) {
|
||||
style_vendor(nc, hm);
|
||||
return;
|
||||
} else if (mg_http_match_uri(hm, "/js/index.js")) {
|
||||
javascript(nc, hm);
|
||||
return;
|
||||
} else if (mg_http_match_uri(hm, "/js/chunk-vendors.js")) {
|
||||
javascript_vendor(nc, hm);
|
||||
} else if (mg_http_match_uri(hm, "/es")) {
|
||||
search(nc, hm);
|
||||
return;
|
||||
} else if (mg_http_match_uri(hm, "/i")) {
|
||||
index_info(nc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (WebCtx.auth0_enabled && !check_auth0(hm)) {
|
||||
mg_http_reply(nc, 403, HTTP_SERVER_HEADER HTTP_TEXT_TYPE_HEADER, "Unauthorized (auth0 error)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mg_http_match_uri(hm, "/es")) {
|
||||
search(nc, hm);
|
||||
} else if (mg_http_match_uri(hm, "/status")) {
|
||||
status(nc);
|
||||
} else if (mg_http_match_uri(hm, "/f/*")) {
|
||||
|
10
src/web/static_generated.c
vendored
10
src/web/static_generated.c
vendored
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user