mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-09-06 17:51:08 +02:00
added sso via passport js
This commit is contained in:
parent
0336b65bca
commit
e7f034ce59
19216
client/package-lock.json
generated
19216
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -15,6 +15,9 @@
|
|||||||
<button type="submit" :disabled="processing" class="bg-blue-600 hover:bg-blue-800 px-8 py-1 mt-3 rounded-md text-white text-center transition duration-300 ease-in-out focus:outline-none">{{ processing ? 'Checking...' : 'Submit' }}</button>
|
<button type="submit" :disabled="processing" class="bg-blue-600 hover:bg-blue-800 px-8 py-1 mt-3 rounded-md text-white text-center transition duration-300 ease-in-out focus:outline-none">{{ processing ? 'Checking...' : 'Submit' }}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div class="w-full flex justify-end">
|
||||||
|
<a href="/oidc/login"><ui-btn :disabled="processing" class="bg-blue-600 hover:bg-blue-800 px-8 py-1 mt-3 rounded-md text-white text-center transition duration-300 ease-in-out focus:outline-none">SSO</ui-btn></a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -87,9 +90,40 @@ export default {
|
|||||||
}
|
}
|
||||||
this.processing = false
|
this.processing = false
|
||||||
},
|
},
|
||||||
|
getCookies() {
|
||||||
|
return document.cookie.split(";").map(c => c.split("=")).reduce((acc, val)=> {
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[val[0]]: val[1]
|
||||||
|
}
|
||||||
|
}, {})
|
||||||
|
},
|
||||||
|
deleteCookie(name, path="/", domain=document.location.host) {
|
||||||
|
document.cookie = name + "=" +
|
||||||
|
((path) ? ";path="+path:"")+
|
||||||
|
((domain)?";domain="+domain:"") +
|
||||||
|
";expires=Thu, 01 Jan 1970 00:00:01 GMT";
|
||||||
|
},
|
||||||
checkAuth() {
|
checkAuth() {
|
||||||
|
if (this.getCookies()["sso"]) {
|
||||||
|
this.processing = true
|
||||||
|
|
||||||
|
this.$axios
|
||||||
|
.$post('/api/authorize', null, {})
|
||||||
|
.then((res) => {
|
||||||
|
console.log({res})
|
||||||
|
this.setUser(res.user)
|
||||||
|
this.processing = false
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Authorize error', error)
|
||||||
|
this.deleteCookie("sso")
|
||||||
|
this.processing = false
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (localStorage.getItem('token')) {
|
if (localStorage.getItem('token')) {
|
||||||
var token = localStorage.getItem('token')
|
let token = localStorage.getItem('token')
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
this.processing = true
|
this.processing = true
|
||||||
|
6
index.js
6
index.js
@ -13,6 +13,12 @@ if (isDev) {
|
|||||||
process.env.AUDIOBOOK_PATH = devEnv.AudiobookPath
|
process.env.AUDIOBOOK_PATH = devEnv.AudiobookPath
|
||||||
process.env.FFMPEG_PATH = devEnv.FFmpegPath
|
process.env.FFMPEG_PATH = devEnv.FFmpegPath
|
||||||
process.env.FFPROBE_PATH = devEnv.FFProbePath
|
process.env.FFPROBE_PATH = devEnv.FFProbePath
|
||||||
|
process.env.OIDC_CLIENT_ID = devEnv.OIDC.ClientID
|
||||||
|
process.env.OIDC_CLIENT_SECRET = devEnv.OIDC.ClientSecret
|
||||||
|
process.env.OIDC_ISSUER = devEnv.OIDC.Issuer
|
||||||
|
process.env.OIDC_AUTHORIZATION_URL = devEnv.OIDC.AuthorizationURL
|
||||||
|
process.env.OIDC_TOKEN_URL = devEnv.OIDC.TokenURL
|
||||||
|
process.env.OIDC_USER_INFO_URL = devEnv.OIDC.UserInfoURL
|
||||||
}
|
}
|
||||||
|
|
||||||
const PORT = process.env.PORT || 80
|
const PORT = process.env.PORT || 80
|
||||||
|
2932
package-lock.json
generated
2932
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -31,11 +31,13 @@
|
|||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"command-line-args": "^5.2.0",
|
"command-line-args": "^5.2.0",
|
||||||
|
"cookie-parser": "^1.4.6",
|
||||||
"date-and-time": "^2.0.1",
|
"date-and-time": "^2.0.1",
|
||||||
"epub": "^1.2.1",
|
"epub": "^1.2.1",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-fileupload": "^1.2.1",
|
"express-fileupload": "^1.2.1",
|
||||||
"express-rate-limit": "^5.3.0",
|
"express-rate-limit": "^5.3.0",
|
||||||
|
"express-session": "^1.17.2",
|
||||||
"fast-sort": "^3.1.1",
|
"fast-sort": "^3.1.1",
|
||||||
"fluent-ffmpeg": "^2.1.2",
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
"fs-extra": "^10.0.0",
|
"fs-extra": "^10.0.0",
|
||||||
@ -47,6 +49,9 @@
|
|||||||
"node-cron": "^3.0.0",
|
"node-cron": "^3.0.0",
|
||||||
"node-ffprobe": "^3.0.0",
|
"node-ffprobe": "^3.0.0",
|
||||||
"node-stream-zip": "^1.15.0",
|
"node-stream-zip": "^1.15.0",
|
||||||
|
"passport": "^0.5.2",
|
||||||
|
"passport-jwt": "^4.0.0",
|
||||||
|
"passport-openidconnect": "^0.1.1",
|
||||||
"podcast": "^1.3.0",
|
"podcast": "^1.3.0",
|
||||||
"read-chunk": "^3.1.0",
|
"read-chunk": "^3.1.0",
|
||||||
"recursive-readdir-async": "^1.1.8",
|
"recursive-readdir-async": "^1.1.8",
|
||||||
@ -54,6 +59,5 @@
|
|||||||
"string-strip-html": "^8.3.0",
|
"string-strip-html": "^8.3.0",
|
||||||
"watcher": "^1.2.0",
|
"watcher": "^1.2.0",
|
||||||
"xml2js": "^0.4.23"
|
"xml2js": "^0.4.23"
|
||||||
},
|
}
|
||||||
"devDependencies": {}
|
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
const bcrypt = require('bcryptjs')
|
const bcrypt = require('bcryptjs')
|
||||||
const jwt = require('jsonwebtoken')
|
const jwt = require('jsonwebtoken')
|
||||||
|
const passport = require('passport')
|
||||||
const Logger = require('./Logger')
|
const Logger = require('./Logger')
|
||||||
|
|
||||||
class Auth {
|
class Auth {
|
||||||
@ -38,7 +39,19 @@ class Auth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async authMiddleware(req, res, next) {
|
async authMiddleware(req, res, next) {
|
||||||
var token = null
|
let token = null;
|
||||||
|
if (req.isAuthenticated && req.isAuthenticated()) {
|
||||||
|
token = req.cookies["token"]
|
||||||
|
const user = await this.verifyToken(token)
|
||||||
|
if (!user) {
|
||||||
|
Logger.error('Verify Token User Not Found', token)
|
||||||
|
return res.sendStatus(404)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.user = user
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
// If using a get request, the token can be passed as a query string
|
// If using a get request, the token can be passed as a query string
|
||||||
if (req.method === 'GET' && req.query && req.query.token) {
|
if (req.method === 'GET' && req.query && req.query.token) {
|
||||||
@ -53,7 +66,8 @@ class Auth {
|
|||||||
return res.sendStatus(401)
|
return res.sendStatus(401)
|
||||||
}
|
}
|
||||||
|
|
||||||
var user = await this.verifyToken(token)
|
|
||||||
|
const user = await this.verifyToken(token)
|
||||||
if (!user) {
|
if (!user) {
|
||||||
Logger.error('Verify Token User Not Found', token)
|
Logger.error('Verify Token User Not Found', token)
|
||||||
return res.sendStatus(404)
|
return res.sendStatus(404)
|
||||||
|
109
server/Server.js
109
server/Server.js
@ -1,17 +1,21 @@
|
|||||||
const Path = require('path')
|
const Path = require('path')
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
|
const cookieParser = require("cookie-parser");
|
||||||
|
var session = require('express-session')
|
||||||
const http = require('http')
|
const http = require('http')
|
||||||
const SocketIO = require('socket.io')
|
const SocketIO = require('socket.io')
|
||||||
const fs = require('fs-extra')
|
const fs = require('fs-extra')
|
||||||
const fileUpload = require('express-fileupload')
|
const fileUpload = require('express-fileupload')
|
||||||
const rateLimit = require('express-rate-limit')
|
const rateLimit = require('express-rate-limit')
|
||||||
|
const passport = require('passport');
|
||||||
|
const OidcStrategy = require('passport-openidconnect').Strategy;
|
||||||
|
|
||||||
const { version } = require('../package.json')
|
const { version } = require('../package.json')
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
const { ScanResult } = require('./utils/constants')
|
const { ScanResult } = require('./utils/constants')
|
||||||
const filePerms = require('./utils/filePerms')
|
const filePerms = require('./utils/filePerms')
|
||||||
const { secondsToTimestamp } = require('./utils/index')
|
const { secondsToTimestamp, getId } = require('./utils/index')
|
||||||
const Logger = require('./Logger')
|
const Logger = require('./Logger')
|
||||||
|
|
||||||
// Classes
|
// Classes
|
||||||
@ -28,6 +32,7 @@ const RssFeeds = require('./RssFeeds')
|
|||||||
const DownloadManager = require('./DownloadManager')
|
const DownloadManager = require('./DownloadManager')
|
||||||
const CoverController = require('./CoverController')
|
const CoverController = require('./CoverController')
|
||||||
const CacheManager = require('./CacheManager')
|
const CacheManager = require('./CacheManager')
|
||||||
|
const User = require('./objects/User')
|
||||||
|
|
||||||
class Server {
|
class Server {
|
||||||
constructor(PORT, UID, GID, CONFIG_PATH, METADATA_PATH, AUDIOBOOK_PATH) {
|
constructor(PORT, UID, GID, CONFIG_PATH, METADATA_PATH, AUDIOBOOK_PATH) {
|
||||||
@ -39,6 +44,10 @@ class Server {
|
|||||||
this.AudiobookPath = Path.normalize(AUDIOBOOK_PATH)
|
this.AudiobookPath = Path.normalize(AUDIOBOOK_PATH)
|
||||||
this.MetadataPath = Path.normalize(METADATA_PATH)
|
this.MetadataPath = Path.normalize(METADATA_PATH)
|
||||||
|
|
||||||
|
console.info(this.ConfigPath)
|
||||||
|
console.info(this.MetadataPath)
|
||||||
|
console.info(this.AudiobookPath)
|
||||||
|
|
||||||
fs.ensureDirSync(CONFIG_PATH, 0o774)
|
fs.ensureDirSync(CONFIG_PATH, 0o774)
|
||||||
fs.ensureDirSync(METADATA_PATH, 0o774)
|
fs.ensureDirSync(METADATA_PATH, 0o774)
|
||||||
fs.ensureDirSync(AUDIOBOOK_PATH, 0o774)
|
fs.ensureDirSync(AUDIOBOOK_PATH, 0o774)
|
||||||
@ -65,6 +74,54 @@ class Server {
|
|||||||
this.io = null
|
this.io = null
|
||||||
|
|
||||||
this.clients = {}
|
this.clients = {}
|
||||||
|
passport.serializeUser((user, next) => {
|
||||||
|
next(null, user);
|
||||||
|
});
|
||||||
|
|
||||||
|
passport.deserializeUser((obj, next) => {
|
||||||
|
next(null, obj);
|
||||||
|
});
|
||||||
|
passport.use(new OidcStrategy({
|
||||||
|
issuer: process.env.OIDC_ISSUER,
|
||||||
|
authorizationURL: process.env.OIDC_AUTHORIZATION_URL,
|
||||||
|
tokenURL: process.env.OIDC_TOKEN_URL,
|
||||||
|
userInfoURL: process.env.OIDC_USER_INFO_URL,
|
||||||
|
clientID: process.env.OIDC_CLIENT_ID,
|
||||||
|
clientSecret: process.env.OIDC_CLIENT_SECRET,
|
||||||
|
callbackURL: '/oidc/callback',
|
||||||
|
scope: "openid email profile"
|
||||||
|
}, async (issuer, profile, cb) => {
|
||||||
|
let user = this.db.users.find(u => u.id === profile.id)
|
||||||
|
if (!user) {
|
||||||
|
// create a user
|
||||||
|
let account = {}
|
||||||
|
account.id = profile.id
|
||||||
|
account.username = profile.username
|
||||||
|
account.type = "guest"
|
||||||
|
account.permissions = {
|
||||||
|
download: false,
|
||||||
|
update: false,
|
||||||
|
delete: false,
|
||||||
|
upload: false,
|
||||||
|
accessAllLibraries: false
|
||||||
|
}
|
||||||
|
account.pash = await this.auth.hashPass(getId(profile.id))
|
||||||
|
account.token = await this.auth.generateAccessToken({ userId: account.id })
|
||||||
|
account.createdAt = Date.now()
|
||||||
|
user = new User(account)
|
||||||
|
const success = await this.db.insertEntity('user', user)
|
||||||
|
if (!success) {
|
||||||
|
cb('Failed to save new user')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!user || !user.isActive) {
|
||||||
|
Logger.debug(`[Auth] Failed login attempt`)
|
||||||
|
cb("Invalid user or password")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cb(null, user)
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
get audiobooks() {
|
get audiobooks() {
|
||||||
@ -144,6 +201,23 @@ class Server {
|
|||||||
app.use(fileUpload())
|
app.use(fileUpload())
|
||||||
app.use(express.urlencoded({ extended: true }));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
app.use(express.json())
|
app.use(express.json())
|
||||||
|
app.use(cookieParser());
|
||||||
|
app.use(session({
|
||||||
|
secret: process.env.TOKEN_SECRET,
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: true
|
||||||
|
}));
|
||||||
|
app.use(passport.initialize());
|
||||||
|
app.use(passport.session());
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (req.method === 'GET' && req.query && req.query.token) {
|
||||||
|
token = req.query.token
|
||||||
|
} else {
|
||||||
|
const authHeader = req.headers['authorization']
|
||||||
|
token = authHeader && authHeader.split(' ')[1]
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// Static path to generated nuxt
|
// Static path to generated nuxt
|
||||||
const distPath = Path.join(global.appRoot, '/client/dist')
|
const distPath = Path.join(global.appRoot, '/client/dist')
|
||||||
@ -219,6 +293,36 @@ class Server {
|
|||||||
|
|
||||||
app.post('/logout', this.authMiddleware.bind(this), this.logout.bind(this))
|
app.post('/logout', this.authMiddleware.bind(this), this.logout.bind(this))
|
||||||
|
|
||||||
|
app.get("/oidc/login", passport.authenticate('openidconnect'))
|
||||||
|
|
||||||
|
app.get("/oidc/callback",
|
||||||
|
passport.authenticate('openidconnect', { failureRedirect: '/oidc/login', failureMessage: true }),
|
||||||
|
async (req, res) => {
|
||||||
|
const token = this.auth.generateAccessToken({userId: req.user.id})
|
||||||
|
res.cookie('token', token, { httpOnly: true /* TODO: Set secure: true */ });
|
||||||
|
res.cookie('sso', true, { httpOnly: false /* TODO: Set secure: true */ });
|
||||||
|
|
||||||
|
res.redirect('/');
|
||||||
|
}
|
||||||
|
// (req, res, next) => {
|
||||||
|
// passport.authenticate('openidconnect', async (err, user, info) => {
|
||||||
|
//
|
||||||
|
// Logger.debug(JSON.stringify({user, info}))
|
||||||
|
//
|
||||||
|
// const token = await this.auth.generateAccessToken({ userId: user.id })
|
||||||
|
// res.cookie('token', token, { httpOnly: true /* TODO: Set secure: true */ });
|
||||||
|
// res.cookie('sso', true, { httpOnly: false /* TODO: Set secure: true */ });
|
||||||
|
//
|
||||||
|
// res.redirect('/');
|
||||||
|
// })(req, res, next)
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
)
|
||||||
|
|
||||||
|
// app.get("/oidc/token", (req, res) => {
|
||||||
|
// req.cookies.get("token")
|
||||||
|
// })
|
||||||
|
|
||||||
app.get('/ping', (req, res) => {
|
app.get('/ping', (req, res) => {
|
||||||
Logger.info('Recieved ping')
|
Logger.info('Recieved ping')
|
||||||
res.json({ success: true })
|
res.json({ success: true })
|
||||||
@ -472,6 +576,9 @@ class Server {
|
|||||||
var { socketId } = req.body
|
var { socketId } = req.body
|
||||||
Logger.info(`[Server] User ${req.user ? req.user.username : 'Unknown'} is logging out with socket ${socketId}`)
|
Logger.info(`[Server] User ${req.user ? req.user.username : 'Unknown'} is logging out with socket ${socketId}`)
|
||||||
|
|
||||||
|
res.clearCookie('sso');
|
||||||
|
res.clearCookie('token');
|
||||||
|
if (req.logout) req.logout();
|
||||||
// Strip user and client from client and client socket
|
// Strip user and client from client and client socket
|
||||||
if (socketId && this.clients[socketId]) {
|
if (socketId && this.clients[socketId]) {
|
||||||
var client = this.clients[socketId]
|
var client = this.clients[socketId]
|
||||||
|
Loading…
Reference in New Issue
Block a user