Skip to content
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
e1ddb95
Inital passportjs integration
lukeIam Mar 24, 2023
be53b31
Merge remote-tracking branch 'origin/master' into auth_passportjs
lukeIam Mar 24, 2023
08676a6
Fix: small problem with this context in Auth.js
lukeIam Mar 24, 2023
62b0940
Added passport-openidconnect implementation
lukeIam Apr 14, 2023
812395b
Merge remote-tracking branch 'origin/master' into auth_passportjs
lukeIam Apr 14, 2023
7010a13
Fixes for passport local and allow empty password
advplyr Apr 16, 2023
8d00647
Merge branch 'master' into auth_passportjs
advplyr Apr 16, 2023
8b68543
Merge
advplyr Apr 29, 2023
4359ca2
Fix XAccel issue
advplyr Apr 29, 2023
95e6fef
Merge remote-tracking branch 'origin/master' into auth_passportjs
lukeIam May 27, 2023
dd9a385
Merge remote-tracking branch 'origin/master' into auth_passportjs
lukeIam Aug 12, 2023
f0f03ef
Merge remote-tracking branch 'origin/master' into auth_passportjs
lukeIam Sep 10, 2023
405c954
Updated + first rough implementation
lukeIam Sep 13, 2023
af4c350
Use a short-time cookie to remember where to callback to
lukeIam Sep 14, 2023
226a774
Merge remote-tracking branch 'origin/master' into auth_passportjs
lukeIam Sep 16, 2023
6aaf3f0
Fix bug with undefined property
lukeIam Sep 16, 2023
91d8451
Remove log messages
lukeIam Sep 16, 2023
7af3033
Fix: ci error - no token sercret
lukeIam Sep 16, 2023
763c0f4
add missing await
lukeIam Sep 16, 2023
942aa93
Fix: local login not possible
lukeIam Sep 16, 2023
0a6cd89
Allow rest mode login (?isRest=true)
lukeIam Sep 17, 2023
51b0750
Merge remote-tracking branch 'origin/master' into auth_passportjs
lukeIam Sep 20, 2023
2c90bba
small refactorings
lukeIam Sep 20, 2023
f6113e8
cookie lifetime
lukeIam Sep 20, 2023
45cf00b
fix openid + jwt auth
lukeIam Sep 20, 2023
2c25f64
Add /auth_methods route
lukeIam Sep 20, 2023
0e75c80
prepare show/hide of login buttons
lukeIam Sep 20, 2023
7a13188
show/hide of login buttons
lukeIam Sep 23, 2023
f42ab45
Update passwordless root user check to user user.type instead of user.id
advplyr Sep 23, 2023
9922294
Fix setting tokenSecret on init
advplyr Sep 23, 2023
f6de373
Update /status endpoint to return available auth methods, fix socket …
advplyr Sep 24, 2023
7ba10db
Update login button openid and google urls
advplyr Sep 24, 2023
e282142
Add authentication page in config, add /auth-settings GET endpoint, r…
advplyr Sep 24, 2023
0d5a30b
Update JWT auth extractors, add state in openid redirect, add back co…
advplyr Sep 25, 2023
2662e8f
Merge branch 'master' into auth_passportjs
advplyr Oct 2, 2023
ab14b56
Merge master
advplyr Nov 1, 2023
828b96b
Add server settings for changing openid button text and auto launchin…
advplyr Nov 2, 2023
f15ed08
Merge branch 'master' into auth_passportjs
advplyr Nov 2, 2023
cfe0c2a
Merge branch 'master' into auth_passportjs
advplyr Nov 3, 2023
840811b
Replace passport openidconnect plugin with openid-client, add JWKS an…
advplyr Nov 4, 2023
1e5d6a5
Added swedish translation of strings
ScuttleSE Nov 5, 2023
61e05e9
Add Swedish language option
advplyr Nov 5, 2023
309ef80
Update /auth/openid endpoint to work with PKCE from mobile
advplyr Nov 5, 2023
c17540e
Add app and serverVersion properties to response from /status
advplyr Nov 5, 2023
f840aa8
Add button to populate openid URLs using the issuer URL
advplyr Nov 5, 2023
e140897
Add match existing user by and auto register settings and UI
advplyr Nov 8, 2023
ee75d67
Matching user by openid sub, email or username based on server settin…
advplyr Nov 8, 2023
078cb08
Merge branch 'master' into auth_passportjs
advplyr Nov 10, 2023
237fe84
Add new API endpoint for updating auth-settings and update passport a…
advplyr Nov 10, 2023
557ef2e
Update /auth/openid endpoints for correct PKCE handling
advplyr Nov 11, 2023
1ad6722
Remove google-oauth passport strategy
advplyr Nov 11, 2023
fb48636
Openid auth failures redirect to login page with error message.
advplyr Nov 11, 2023
56c574c
Update package-lock
advplyr Nov 19, 2023
4c2c320
Remove global CORS for api endpoints and setup temp CORS check for eb…
advplyr Nov 19, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions client/components/app/ConfigSideNav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ export default {
id: 'config-rss-feeds',
title: this.$strings.HeaderRSSFeeds,
path: '/config/rss-feeds'
},
{
id: 'config-authentication',
title: this.$strings.HeaderAuthentication,
path: '/config/authentication'
}
]

Expand Down
1 change: 1 addition & 0 deletions client/pages/config.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export default {
else if (pageName === 'item-metadata-utils') return this.$strings.HeaderItemMetadataUtils
else if (pageName === 'rss-feeds') return this.$strings.HeaderRSSFeeds
else if (pageName === 'email') return this.$strings.HeaderEmail
else if (pageName === 'authentication') return this.$strings.HeaderAuthentication
}
return this.$strings.HeaderSettings
}
Expand Down
138 changes: 138 additions & 0 deletions client/pages/config/authentication.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<template>
<div>
<app-settings-content :header-text="$strings.HeaderAuthentication">
<div class="w-full border border-white/10 rounded-xl p-4 my-4 bg-primary/25">
<div class="flex items-center">
<ui-checkbox v-model="enableLocalAuth" checkbox-bg="bg" />
<p class="text-lg pl-4">Password Authentication</p>
</div>
</div>
<div class="w-full border border-white/10 rounded-xl p-4 my-4 bg-primary/25">
<div class="flex items-center">
<ui-checkbox v-model="enableOpenIDAuth" checkbox-bg="bg" />
<p class="text-lg pl-4">OpenID Connect Authentication</p>
</div>
<div class="overflow-hidden">
<transition name="slide">
<div v-if="enableOpenIDAuth" class="flex flex-wrap pt-4">
<ui-text-input-with-label ref="issuerUrl" v-model="newAuthSettings.authOpenIDIssuerURL" :disabled="savingSettings" :label="'Issuer URL'" class="mb-2" />

<ui-text-input-with-label ref="authorizationUrl" v-model="newAuthSettings.authOpenIDAuthorizationURL" :disabled="savingSettings" :label="'Authorize URL'" class="mb-2" />

<ui-text-input-with-label ref="tokenUrl" v-model="newAuthSettings.authOpenIDTokenURL" :disabled="savingSettings" :label="'Token URL'" class="mb-2" />

<ui-text-input-with-label ref="userInfoUrl" v-model="newAuthSettings.authOpenIDUserInfoURL" :disabled="savingSettings" :label="'Userinfo URL'" class="mb-2" />

<ui-text-input-with-label ref="openidClientId" v-model="newAuthSettings.authOpenIDClientID" :disabled="savingSettings" :label="'Client ID'" class="mb-2" />

<ui-text-input-with-label ref="openidClientSecret" v-model="newAuthSettings.authOpenIDClientSecret" :disabled="savingSettings" :label="'Client Secret'" class="mb-2" />
</div>
</transition>
</div>
</div>
<div class="w-full flex items-center justify-end p-4">
<ui-btn color="success" :padding-x="8" small class="text-base" :loading="savingSettings" @click="saveSettings">{{ $strings.ButtonSave }}</ui-btn>
</div>
</app-settings-content>
</div>
</template>

<script>
export default {
async asyncData({ store, redirect, app }) {
if (!store.getters['user/getIsAdminOrUp']) {
redirect('/')
return
}

const authSettings = await app.$axios.$get('/api/auth-settings').catch((error) => {
console.error('Failed', error)
return null
})
if (!authSettings) {
redirect('/config')
return
}
return {
authSettings
}
},
data() {
return {
enableLocalAuth: false,
enableOpenIDAuth: false,
savingSettings: false,
newAuthSettings: {}
}
},
computed: {
authMethods() {
return this.authSettings.authActiveAuthMethods || []
}
},
methods: {
validateOpenID() {
let isValid = true
if (!this.newAuthSettings.authOpenIDIssuerURL) {
this.$toast.error('Issuer URL required')
isValid = false
}
if (!this.newAuthSettings.authOpenIDAuthorizationURL) {
this.$toast.error('Authorize URL required')
isValid = false
}
if (!this.newAuthSettings.authOpenIDTokenURL) {
this.$toast.error('Token URL required')
isValid = false
}
if (!this.newAuthSettings.authOpenIDUserInfoURL) {
this.$toast.error('Userinfo URL required')
isValid = false
}
if (!this.newAuthSettings.authOpenIDClientID) {
this.$toast.error('Client ID required')
isValid = false
}
if (!this.newAuthSettings.authOpenIDClientSecret) {
this.$toast.error('Client Secret required')
isValid = false
}
return isValid
},
async saveSettings() {
if (!this.enableLocalAuth && !this.enableOpenIDAuth) {
this.$toast.error('Must have at least one authentication method enabled')
return
}

if (this.enableOpenIDAuth && !this.validateOpenID()) {
return
}

this.newAuthSettings.authActiveAuthMethods = []
if (this.enableLocalAuth) this.newAuthSettings.authActiveAuthMethods.push('local')
if (this.enableOpenIDAuth) this.newAuthSettings.authActiveAuthMethods.push('openid')

this.savingSettings = true
const success = await this.$store.dispatch('updateServerSettings', this.newAuthSettings)
this.savingSettings = false
if (success) {
this.$toast.success('Server settings updated')
} else {
this.$toast.error('Failed to update server settings')
}
},
init() {
this.newAuthSettings = {
...this.authSettings
}
this.enableLocalAuth = this.authMethods.includes('local')
this.enableOpenIDAuth = this.authMethods.includes('openid')
}
},
mounted() {
this.init()
}
}
</script>

68 changes: 58 additions & 10 deletions client/pages/login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@
</div>
<div v-else-if="isInit" class="w-full max-w-md px-8 pb-8 pt-4 -mt-40">
<p class="text-3xl text-white text-center mb-4">{{ $strings.HeaderLogin }}</p>

<div class="w-full h-px bg-white bg-opacity-10 my-4" />

<p v-if="error" class="text-error text-center py-2">{{ error }}</p>
<form @submit.prevent="submitForm">

<form v-show="login_local" @submit.prevent="submitForm">
<label class="text-xs text-gray-300 uppercase">{{ $strings.LabelUsername }}</label>
<ui-text-input v-model="username" :disabled="processing" class="mb-3 w-full" />

Expand All @@ -37,6 +40,17 @@
<ui-btn type="submit" :disabled="processing" color="primary" class="leading-none">{{ processing ? 'Checking...' : $strings.ButtonSubmit }}</ui-btn>
</div>
</form>

<div v-if="login_local && (login_google_oauth20 || login_openid)" class="w-full h-px bg-white bg-opacity-10 my-4" />

<div class="w-full flex py-3">
<a v-show="login_google_oauth20" :href="googleAuthUri">
<ui-btn color="primary" class="leading-none">Login with Google</ui-btn>
</a>
<a v-show="login_openid" :href="openidAuthUri">
<ui-btn color="primary" class="leading-none">Login with OpenId</ui-btn>
</a>
</div>
</div>
</div>
</div>
Expand All @@ -60,7 +74,10 @@ export default {
},
confirmPassword: '',
ConfigPath: '',
MetadataPath: ''
MetadataPath: '',
login_local: true,
login_google_oauth20: false,
login_openid: false
}
},
watch: {
Expand Down Expand Up @@ -93,6 +110,12 @@ export default {
computed: {
user() {
return this.$store.state.user.user
},
googleAuthUri() {
return `${process.env.serverUrl}/auth/google?callback=${location.toString()}`
},
openidAuthUri() {
return `${process.env.serverUrl}/auth/openid?callback=${location.toString()}`
}
},
methods: {
Expand Down Expand Up @@ -162,6 +185,7 @@ export default {
else this.error = 'Unknown Error'
return false
})

if (authRes?.error) {
this.error = authRes.error
} else if (authRes) {
Expand Down Expand Up @@ -196,28 +220,52 @@ export default {
this.processing = true
this.$axios
.$get('/status')
.then((res) => {
.then((data) => {
this.processing = false
this.isInit = res.isInit
this.showInitScreen = !res.isInit
this.$setServerLanguageCode(res.language)
this.isInit = data.isInit
this.showInitScreen = !data.isInit
this.$setServerLanguageCode(data.language)
if (this.showInitScreen) {
this.ConfigPath = res.ConfigPath || ''
this.MetadataPath = res.MetadataPath || ''
this.ConfigPath = data.ConfigPath || ''
this.MetadataPath = data.MetadataPath || ''
} else {
this.updateLoginVisibility(data.authMethods || [])
}
})
.catch((error) => {
console.error('Status check failed', error)
this.processing = false
this.criticalError = 'Status check failed'
})
},
updateLoginVisibility(authMethods) {
if (authMethods.includes('local') || !authMethods.length) {
this.login_local = true
} else {
this.login_local = false
}

if (authMethods.includes('google-oauth20')) {
this.login_google_oauth20 = true
} else {
this.login_google_oauth20 = false
}

if (authMethods.includes('openid')) {
this.login_openid = true
} else {
this.login_openid = false
}
}
},
async mounted() {
if (new URLSearchParams(window.location.search).get('setToken')) {
localStorage.setItem('token', new URLSearchParams(window.location.search).get('setToken'))
}
if (localStorage.getItem('token')) {
var userfound = await this.checkAuth()
if (userfound) return // if valid user no need to check status
if (await this.checkAuth()) return // if valid user no need to check status
}

this.checkStatus()
}
}
Expand Down
2 changes: 1 addition & 1 deletion client/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const getters = {

export const actions = {
updateServerSettings({ commit }, payload) {
var updatePayload = {
const updatePayload = {
...payload
}
return this.$axios.$patch('/api/settings', updatePayload).then((result) => {
Expand Down
1 change: 1 addition & 0 deletions client/strings/en-us.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"HeaderAppriseNotificationSettings": "Apprise Notification Settings",
"HeaderAudiobookTools": "Audiobook File Management Tools",
"HeaderAudioTracks": "Audio Tracks",
"HeaderAuthentication": "Authentication",
"HeaderBackups": "Backups",
"HeaderChangePassword": "Change Password",
"HeaderChapters": "Chapters",
Expand Down
Loading