mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-10 11:21:36 +00:00
Merge 9b7e21a87e into 13dd4edd6a
This commit is contained in:
commit
b115835627
8 changed files with 227 additions and 10 deletions
|
|
@ -108,6 +108,21 @@
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</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="newAuthSettings.authForwardAuthEnabled" checkbox-bg="bg" />
|
||||||
|
<p class="text-lg pl-4">{{ $strings.HeaderForwardAuthentication }}</p>
|
||||||
|
</div>
|
||||||
|
<transition name="slide">
|
||||||
|
<div v-if="newAuthSettings.authForwardAuthEnabled" class="flex flex-wrap pt-4">
|
||||||
|
<p class="text-lg text-gray-300 mb-2">{{ $strings.LabelForwardAuthenticationWarning }}</p>
|
||||||
|
<ui-text-input-with-label ref="forwardAuthPattern" v-model="newAuthSettings.authForwardAuthPattern" :disabled="savingSettings" :label="'IP Pattern'" class="mb-2" />
|
||||||
|
<p class="sm:pl-4 text-sm text-gray-300 mb-2" v-html="$strings.LabelForwardAuthenticationPatternDescription" />
|
||||||
|
<ui-text-input-with-label ref="forwardAuthPath" v-model="newAuthSettings.authForwardAuthPath" :disabled="savingSettings" :label="'Logout Path'" class="mb-2" />
|
||||||
|
<p class="sm:pl-4 text-sm text-gray-300 mb-2">{{ $strings.LabelForwardAuthenticationLogoutDescription }}</p>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
<div class="w-full flex items-center justify-end p-4">
|
<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>
|
<ui-btn color="success" :padding-x="8" small class="text-base" :loading="savingSettings" @click="saveSettings">{{ $strings.ButtonSave }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -116,6 +131,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { isPrivate } from 'ip'
|
||||||
|
import { isV4Format, isV6Format } from 'ip'
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async asyncData({ store, redirect, app }) {
|
async asyncData({ store, redirect, app }) {
|
||||||
if (!store.getters['user/getIsAdminOrUp']) {
|
if (!store.getters['user/getIsAdminOrUp']) {
|
||||||
|
|
@ -139,6 +158,7 @@ export default {
|
||||||
return {
|
return {
|
||||||
enableLocalAuth: false,
|
enableLocalAuth: false,
|
||||||
enableOpenIDAuth: false,
|
enableOpenIDAuth: false,
|
||||||
|
enableForwardAuth: false,
|
||||||
showCustomLoginMessage: false,
|
showCustomLoginMessage: false,
|
||||||
savingSettings: false,
|
savingSettings: false,
|
||||||
openIdSigningAlgorithmsSupportedByIssuer: [],
|
openIdSigningAlgorithmsSupportedByIssuer: [],
|
||||||
|
|
@ -286,8 +306,48 @@ export default {
|
||||||
|
|
||||||
return isValid
|
return isValid
|
||||||
},
|
},
|
||||||
|
|
||||||
|
validateForwardAuth(input){
|
||||||
|
let cidr = '';
|
||||||
|
let address = input;
|
||||||
|
let message = undefined;
|
||||||
|
console.log(input)
|
||||||
|
// Check to see if a cidr is included
|
||||||
|
if(input.includes('/')){
|
||||||
|
[address, cidr] = input.split('/');
|
||||||
|
}
|
||||||
|
if(isV4Format(address)) {
|
||||||
|
if(cidr && (cidr < 0 || cidr > 32)){
|
||||||
|
message = `'${cidr}' is an invalid CIDR range for ipv4, a valid range is between 0 and 32`;
|
||||||
|
}
|
||||||
|
else if (!cidr) {
|
||||||
|
// default to 32 if no cidr is provided
|
||||||
|
cidr = 32;
|
||||||
|
}
|
||||||
|
} else if (isV6Format(address)) {
|
||||||
|
if(cidr && (cidr < 0 || cidr > 128)){
|
||||||
|
message = `'${cidr}' is an invalid CIDR range for ipv6, a valid range is between 0 and 128`;
|
||||||
|
}else if (!cidr) {
|
||||||
|
// default to 128 if no cidr is provided
|
||||||
|
cidr = 128;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message = 'Address is not a valid ipv4 or ipv6 address';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!message && !isPrivate(address)){
|
||||||
|
message = `Address '${address}' is not a private address. For security reasons, only private addresses are allowed.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
message,
|
||||||
|
address,
|
||||||
|
cidr
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
async saveSettings() {
|
async saveSettings() {
|
||||||
if (!this.enableLocalAuth && !this.enableOpenIDAuth) {
|
if (!this.enableLocalAuth && !this.enableOpenIDAuth && !this.enableForwardAuth) {
|
||||||
this.$toast.error('Must have at least one authentication method enabled')
|
this.$toast.error('Must have at least one authentication method enabled')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -296,6 +356,18 @@ export default {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(this.newAuthSettings.authForwardAuthEnabled){
|
||||||
|
const validationResults = this.validateForwardAuth(this.newAuthSettings.authForwardAuthPattern);
|
||||||
|
console.log(validationResults);
|
||||||
|
// if there is a message, then there was an error
|
||||||
|
if(validationResults.message){
|
||||||
|
this.$toast.error(validationResults.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// ensure the address is in IP/CIDR format
|
||||||
|
this.newAuthSettings.authForwardAuthPattern = `${validationResults.address}/${validationResults.cidr}`;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.showCustomLoginMessage || !this.newAuthSettings.authLoginCustomMessage?.trim()) {
|
if (!this.showCustomLoginMessage || !this.newAuthSettings.authLoginCustomMessage?.trim()) {
|
||||||
this.newAuthSettings.authLoginCustomMessage = null
|
this.newAuthSettings.authLoginCustomMessage = null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,7 @@
|
||||||
"HeaderEreaderSettings": "Ereader Settings",
|
"HeaderEreaderSettings": "Ereader Settings",
|
||||||
"HeaderFiles": "Files",
|
"HeaderFiles": "Files",
|
||||||
"HeaderFindChapters": "Find Chapters",
|
"HeaderFindChapters": "Find Chapters",
|
||||||
|
"HeaderForwardAuthentication": "Forward Authentication",
|
||||||
"HeaderIgnoredFiles": "Ignored Files",
|
"HeaderIgnoredFiles": "Ignored Files",
|
||||||
"HeaderItemFiles": "Item Files",
|
"HeaderItemFiles": "Item Files",
|
||||||
"HeaderItemMetadataUtils": "Item Metadata Utils",
|
"HeaderItemMetadataUtils": "Item Metadata Utils",
|
||||||
|
|
@ -350,6 +351,9 @@
|
||||||
"LabelFontScale": "Font scale",
|
"LabelFontScale": "Font scale",
|
||||||
"LabelFontStrikethrough": "Strikethrough",
|
"LabelFontStrikethrough": "Strikethrough",
|
||||||
"LabelFormat": "Format",
|
"LabelFormat": "Format",
|
||||||
|
"LabelForwardAuthenticationWarning": "Note: Enabling forward authentication will block the mobile application from working.",
|
||||||
|
"LabelForwardAuthenticationLogoutDescription": "The path to logout from the forward authentication provider. Ignored if empty.",
|
||||||
|
"LabelForwardAuthenticationPatternDescription": "Input must be in for format of <code>IP/CIDR</code>. For security reasons only private IPv4/IPv6 ranges are permitted. If a CIDR is not provided <code>/32</code> or <code>/128</code> will be assumed.",
|
||||||
"LabelGenre": "Genre",
|
"LabelGenre": "Genre",
|
||||||
"LabelGenres": "Genres",
|
"LabelGenres": "Genres",
|
||||||
"LabelHardDeleteFile": "Hard delete file",
|
"LabelHardDeleteFile": "Hard delete file",
|
||||||
|
|
|
||||||
9
package-lock.json
generated
9
package-lock.json
generated
|
|
@ -15,6 +15,7 @@
|
||||||
"express-session": "^1.17.3",
|
"express-session": "^1.17.3",
|
||||||
"graceful-fs": "^4.2.10",
|
"graceful-fs": "^4.2.10",
|
||||||
"htmlparser2": "^8.0.1",
|
"htmlparser2": "^8.0.1",
|
||||||
|
"ip": "^2.0.1",
|
||||||
"lru-cache": "^10.0.3",
|
"lru-cache": "^10.0.3",
|
||||||
"node-unrar-js": "^2.0.2",
|
"node-unrar-js": "^2.0.2",
|
||||||
"nodemailer": "^6.9.13",
|
"nodemailer": "^6.9.13",
|
||||||
|
|
@ -2379,10 +2380,10 @@
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||||
},
|
},
|
||||||
"node_modules/ip": {
|
"node_modules/ip": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
|
||||||
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==",
|
"integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==",
|
||||||
"optional": true
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/ipaddr.js": {
|
"node_modules/ipaddr.js": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@
|
||||||
"express-session": "^1.17.3",
|
"express-session": "^1.17.3",
|
||||||
"graceful-fs": "^4.2.10",
|
"graceful-fs": "^4.2.10",
|
||||||
"htmlparser2": "^8.0.1",
|
"htmlparser2": "^8.0.1",
|
||||||
|
"ip": "^2.0.1",
|
||||||
"lru-cache": "^10.0.3",
|
"lru-cache": "^10.0.3",
|
||||||
"node-unrar-js": "^2.0.2",
|
"node-unrar-js": "^2.0.2",
|
||||||
"nodemailer": "^6.9.13",
|
"nodemailer": "^6.9.13",
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ const ExtractJwt = require('passport-jwt').ExtractJwt
|
||||||
const OpenIDClient = require('openid-client')
|
const OpenIDClient = require('openid-client')
|
||||||
const Database = require('./Database')
|
const Database = require('./Database')
|
||||||
const Logger = require('./Logger')
|
const Logger = require('./Logger')
|
||||||
|
const ForwardStrategy = require('./utils/ForwardStrategy')
|
||||||
|
const ip = require('ip')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Class for handling all the authentication related functionality.
|
* @class Class for handling all the authentication related functionality.
|
||||||
|
|
@ -34,6 +36,9 @@ class Auth {
|
||||||
this.initAuthStrategyOpenID()
|
this.initAuthStrategyOpenID()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always load the forward strategy
|
||||||
|
passport.use('forward', new ForwardStrategy(this.forwardAuthCheck.bind(this)))
|
||||||
|
|
||||||
// Load the JwtStrategy (always) -> for bearer token auth
|
// Load the JwtStrategy (always) -> for bearer token auth
|
||||||
passport.use(
|
passport.use(
|
||||||
new JwtStrategy(
|
new JwtStrategy(
|
||||||
|
|
@ -73,6 +78,35 @@ class Auth {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async forwardAuthCheck(_req, user, ipAddress, done) {
|
||||||
|
const ipPattern = Database.serverSettings.authForwardAuthPattern
|
||||||
|
if (!Database.serverSettings.authForwardAuthEnabled) {
|
||||||
|
// silently deny access via forward strategy
|
||||||
|
return done(null, null)
|
||||||
|
}
|
||||||
|
if (!ipPattern) {
|
||||||
|
Logger.warn(`[Auth] Forward strategy: Unauthorized access from ${ipAddress} User: ${user} Reason: No IP pattern`)
|
||||||
|
return done(null, null)
|
||||||
|
}
|
||||||
|
if (!ip.isPrivate(ipAddress)) {
|
||||||
|
Logger.warn(`[Auth] Forward strategy: Unauthorized access from ${ipAddress} User: ${user} Reason: IP not private`)
|
||||||
|
return done(null, null)
|
||||||
|
}
|
||||||
|
if (!ip.cidrSubnet(ipPattern).contains(ipAddress)) {
|
||||||
|
Logger.warn(`[Auth] Forward strategy: Unauthorized access from ${ipAddress} User: ${user} Reason: IP not in range`)
|
||||||
|
return done(null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const userObj = await Database.userModel.getUserByUsername(user.toLowerCase())
|
||||||
|
|
||||||
|
if (!userObj) {
|
||||||
|
Logger.info(`[Auth] Forward strategy: User not found: ${user}`)
|
||||||
|
return done(null, null)
|
||||||
|
}
|
||||||
|
Logger.debug(`[Auth] Forward strategy: User found: ${user}`)
|
||||||
|
return done(null, userObj)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Passport use LocalStrategy
|
* Passport use LocalStrategy
|
||||||
*/
|
*/
|
||||||
|
|
@ -699,6 +733,11 @@ class Auth {
|
||||||
|
|
||||||
let logoutUrl = null
|
let logoutUrl = null
|
||||||
|
|
||||||
|
if (Database.serverSettings.authForwardAuthEnabled && Database.serverSettings.authForwardAuthPath) {
|
||||||
|
// Forward auth logout redirect
|
||||||
|
logoutUrl = Database.serverSettings.authForwardAuthPath
|
||||||
|
}
|
||||||
|
|
||||||
if (authMethod === 'openid' || authMethod === 'openid-mobile') {
|
if (authMethod === 'openid' || authMethod === 'openid-mobile') {
|
||||||
// If we are using openid, we need to redirect to the logout endpoint
|
// If we are using openid, we need to redirect to the logout endpoint
|
||||||
// node-openid-client does not support doing it over passport
|
// node-openid-client does not support doing it over passport
|
||||||
|
|
@ -713,7 +752,7 @@ class Auth {
|
||||||
const host = req.get('host')
|
const host = req.get('host')
|
||||||
// TODO: ABS does currently not support subfolders for installation
|
// TODO: ABS does currently not support subfolders for installation
|
||||||
// If we want to support it we need to include a config for the serverurl
|
// If we want to support it we need to include a config for the serverurl
|
||||||
postLogoutRedirectUri = `${protocol}://${host}/login`
|
postLogoutRedirectUri = logoutUrl || `${protocol}://${host}/login`
|
||||||
}
|
}
|
||||||
// else for openid-mobile we keep postLogoutRedirectUri on null
|
// else for openid-mobile we keep postLogoutRedirectUri on null
|
||||||
// nice would be to redirect to the app here, but for example Authentik does not implement
|
// nice would be to redirect to the app here, but for example Authentik does not implement
|
||||||
|
|
@ -752,8 +791,8 @@ class Auth {
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
next()
|
next()
|
||||||
} else {
|
} else {
|
||||||
// try JWT to authenticate
|
// try JWT and forward to authenticate
|
||||||
passport.authenticate('jwt')(req, res, next)
|
passport.authenticate(['jwt', 'forward'])(req, res, next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -634,7 +634,30 @@ class MiscController {
|
||||||
for (const key in currentAuthenticationSettings) {
|
for (const key in currentAuthenticationSettings) {
|
||||||
if (settingsUpdate[key] === undefined) continue
|
if (settingsUpdate[key] === undefined) continue
|
||||||
|
|
||||||
if (key === 'authActiveAuthMethods') {
|
if (key === 'authForwardAuthPattern') {
|
||||||
|
const updatedValue = settingsUpdate[key]
|
||||||
|
if (updatedValue === '') updatedValue = null
|
||||||
|
let currentValue = currentAuthenticationSettings[key]
|
||||||
|
if (currentValue === '') currentValue = null
|
||||||
|
|
||||||
|
if (updatedValue !== currentValue) {
|
||||||
|
Logger.debug(`[MiscController] Updating auth settings key "${key}" from "${currentValue}" to "${updatedValue}"`)
|
||||||
|
Database.serverSettings[key] = updatedValue
|
||||||
|
hasUpdates = true
|
||||||
|
}
|
||||||
|
} else if (key === 'authForwardAuthEnabled') {
|
||||||
|
if (typeof settingsUpdate[key] !== 'boolean') {
|
||||||
|
Logger.warn(`[MiscController] Invalid value for ${key}. Expected boolean`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let updatedValue = settingsUpdate[key]
|
||||||
|
let currentValue = currentAuthenticationSettings[key]
|
||||||
|
if (updatedValue !== currentValue) {
|
||||||
|
Logger.debug(`[MiscController] Updating auth settings key "${key}" from "${currentValue}" to "${updatedValue}"`)
|
||||||
|
Database.serverSettings[key] = updatedValue
|
||||||
|
hasUpdates = true
|
||||||
|
}
|
||||||
|
} else if (key === 'authActiveAuthMethods') {
|
||||||
let updatedAuthMethods = settingsUpdate[key]?.filter?.((authMeth) => Database.serverSettings.supportedAuthMethods.includes(authMeth))
|
let updatedAuthMethods = settingsUpdate[key]?.filter?.((authMeth) => Database.serverSettings.supportedAuthMethods.includes(authMeth))
|
||||||
if (Array.isArray(updatedAuthMethods) && updatedAuthMethods.length) {
|
if (Array.isArray(updatedAuthMethods) && updatedAuthMethods.length) {
|
||||||
updatedAuthMethods.sort()
|
updatedAuthMethods.sort()
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,11 @@ class ServerSettings {
|
||||||
this.authOpenIDGroupClaim = ''
|
this.authOpenIDGroupClaim = ''
|
||||||
this.authOpenIDAdvancedPermsClaim = ''
|
this.authOpenIDAdvancedPermsClaim = ''
|
||||||
|
|
||||||
|
// Forward Auth
|
||||||
|
this.authForwardAuthPattern = '127.0.0.1/32' // default to exact localhost
|
||||||
|
this.authForwardAuthPath = ''
|
||||||
|
this.authForwardAuthEnabled = false
|
||||||
|
|
||||||
if (settings) {
|
if (settings) {
|
||||||
this.construct(settings)
|
this.construct(settings)
|
||||||
}
|
}
|
||||||
|
|
@ -155,6 +160,18 @@ class ServerSettings {
|
||||||
this.authActiveAuthMethods = ['local']
|
this.authActiveAuthMethods = ['local']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (settings.authForwardAuthPattern != undefined) {
|
||||||
|
this.authForwardAuthPattern = settings.authForwardAuthPattern
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.authForwardAuthEnabled != undefined) {
|
||||||
|
this.authForwardAuthEnabled = settings.authForwardAuthEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.authForwardAuthPath != undefined) {
|
||||||
|
this.authForwardAuthPath = settings.authForwardAuthPath
|
||||||
|
}
|
||||||
|
|
||||||
// Migrations
|
// Migrations
|
||||||
if (settings.storeCoverWithBook != undefined) {
|
if (settings.storeCoverWithBook != undefined) {
|
||||||
// storeCoverWithBook was renamed to storeCoverWithItem in 2.0.0
|
// storeCoverWithBook was renamed to storeCoverWithItem in 2.0.0
|
||||||
|
|
@ -240,7 +257,10 @@ class ServerSettings {
|
||||||
authOpenIDMatchExistingBy: this.authOpenIDMatchExistingBy,
|
authOpenIDMatchExistingBy: this.authOpenIDMatchExistingBy,
|
||||||
authOpenIDMobileRedirectURIs: this.authOpenIDMobileRedirectURIs, // Do not return to client
|
authOpenIDMobileRedirectURIs: this.authOpenIDMobileRedirectURIs, // Do not return to client
|
||||||
authOpenIDGroupClaim: this.authOpenIDGroupClaim, // Do not return to client
|
authOpenIDGroupClaim: this.authOpenIDGroupClaim, // Do not return to client
|
||||||
authOpenIDAdvancedPermsClaim: this.authOpenIDAdvancedPermsClaim // Do not return to client
|
authOpenIDAdvancedPermsClaim: this.authOpenIDAdvancedPermsClaim, // Do not return to client
|
||||||
|
authForwardAuthPattern: this.authForwardAuthPattern,
|
||||||
|
authForwardAuthPath: this.authForwardAuthPath,
|
||||||
|
authForwardAuthEnabled: this.authForwardAuthEnabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -287,6 +307,10 @@ class ServerSettings {
|
||||||
authOpenIDGroupClaim: this.authOpenIDGroupClaim, // Do not return to client
|
authOpenIDGroupClaim: this.authOpenIDGroupClaim, // Do not return to client
|
||||||
authOpenIDAdvancedPermsClaim: this.authOpenIDAdvancedPermsClaim, // Do not return to client
|
authOpenIDAdvancedPermsClaim: this.authOpenIDAdvancedPermsClaim, // Do not return to client
|
||||||
|
|
||||||
|
authForwardAuthPattern: this.authForwardAuthPattern,
|
||||||
|
authForwardAuthPath: this.authForwardAuthPath,
|
||||||
|
authForwardAuthEnabled: this.authForwardAuthEnabled,
|
||||||
|
|
||||||
authOpenIDSamplePermissions: User.getSampleAbsPermissions()
|
authOpenIDSamplePermissions: User.getSampleAbsPermissions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
53
server/utils/ForwardStrategy.js
Normal file
53
server/utils/ForwardStrategy.js
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
const passport = require('passport')
|
||||||
|
const requestIp = require('../libs/requestIp')
|
||||||
|
|
||||||
|
class ForwardStrategy extends passport.Strategy {
|
||||||
|
/**
|
||||||
|
* Creates a new ForwardStrategy instance.
|
||||||
|
* A ForwardStrategy instance authenticates requests based on the contents of the `X-Forwarded-User` header
|
||||||
|
*
|
||||||
|
* @param {Function} verify The function to call to verify the user.
|
||||||
|
*/
|
||||||
|
constructor(options, verify) {
|
||||||
|
super()
|
||||||
|
// if verify is not provided, assume the first argument is the verify function
|
||||||
|
if (!verify && typeof options === 'function') {
|
||||||
|
verify = options
|
||||||
|
} else if (!verify) {
|
||||||
|
throw new TypeError('ForwardStrategy requires a verify callback')
|
||||||
|
}
|
||||||
|
this.name = 'forward'
|
||||||
|
this._verify = verify
|
||||||
|
this._header = options.header || 'x-forwarded-user'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticate request based on the contents of the `X-Forwarded-User` header.
|
||||||
|
* @param {*} req The request to authenticate.
|
||||||
|
* @returns {void} Calls `success`, `fail`, or `error` based on the result of the authentication.
|
||||||
|
*/
|
||||||
|
authenticate(req) {
|
||||||
|
const username = req.headers[this._header]
|
||||||
|
const ipAddress = requestIp.getClientIp(req)
|
||||||
|
|
||||||
|
if (!username) {
|
||||||
|
return this.fail('No username found')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ipAddress) {
|
||||||
|
return this.fail('No IP address found')
|
||||||
|
}
|
||||||
|
|
||||||
|
this._verify(req, username, ipAddress, (err, user) => {
|
||||||
|
if (err) {
|
||||||
|
return this.error(err)
|
||||||
|
}
|
||||||
|
if (!user) {
|
||||||
|
return this.fail('No user found')
|
||||||
|
}
|
||||||
|
this.success(user)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ForwardStrategy
|
||||||
Loading…
Add table
Add a link
Reference in a new issue