diff --git a/client/pages/login.vue b/client/pages/login.vue index 52feedb6..27bdc63b 100644 --- a/client/pages/login.vue +++ b/client/pages/login.vue @@ -37,6 +37,15 @@ {{ processing ? 'Checking...' : $strings.ButtonSubmit }} +
+
+ + Login with Google + + + Login with OpenId + +
@@ -132,11 +141,7 @@ export default { location.reload() }, -<<<<<<< HEAD - setUser({ user, userDefaultLibraryId, serverSettings, Source }) { -======= setUser({ user, userDefaultLibraryId, serverSettings, Source, ereaderDevices }) { ->>>>>>> origin/master this.$store.commit('setServerSettings', serverSettings) this.$store.commit('setSource', Source) this.$store.commit('libraries/setEReaderDevices', ereaderDevices) @@ -166,10 +171,7 @@ export default { else this.error = 'Unknown Error' return false }) -<<<<<<< HEAD console.log('Auth res', authRes) -======= ->>>>>>> origin/master if (authRes?.error) { this.error = authRes.error } else if (authRes) { @@ -222,6 +224,11 @@ export default { } }, async mounted() { + console.log(new URLSearchParams(window.location.search).get('setToken')) + if (new URLSearchParams(window.location.search).get('setToken')) { + localStorage.setItem('token', new URLSearchParams(window.location.search).get('setToken')) + console.log('hereasd') + } if (localStorage.getItem('token')) { var userfound = await this.checkAuth() if (userfound) return // if valid user no need to check status diff --git a/server/Auth.js b/server/Auth.js index 0885c88a..ceeddb36 100644 --- a/server/Auth.js +++ b/server/Auth.js @@ -6,14 +6,14 @@ const JwtStrategy = require('passport-jwt').Strategy const ExtractJwt = require('passport-jwt').ExtractJwt const GoogleStrategy = require('passport-google-oauth20').Strategy const OpenIDConnectStrategy = require('passport-openidconnect') +const Database = require('./Database') /** * @class Class for handling all the authentication related functionality. */ class Auth { - constructor(db) { - this.db = db + constructor() { } /** @@ -31,10 +31,10 @@ class Auth { clientID: global.ServerSettings.authGoogleOauth20ClientID, clientSecret: global.ServerSettings.authGoogleOauth20ClientSecret, callbackURL: global.ServerSettings.authGoogleOauth20CallbackURL - }, (function (accessToken, refreshToken, profile, done) { + }, (async function (accessToken, refreshToken, profile, done) { // TODO: what to use as username // TODO: do we want to create the users which does not exist? - const user = this.db.users.find(u => u.username.toLowerCase() === profile.emails[0].value.toLowerCase()) + const user = await Database.userModel.getUserByEmail(profile.emails[0].value.toLowerCase()) if (!user || !user.isActive) { done(null, null) @@ -61,7 +61,7 @@ class Auth { (function (issuer, profile, done) { // TODO: what to use as username // TODO: do we want to create the users which does not exist? - var user = this.db.users.find(u => u.username.toLowerCase() === profile.emails[0].value.toLowerCase()) + var user = Database.userModel.getUserByEmail(profile.emails[0].value.toLowerCase()) if (!user || !user.isActive) { done(null, null) @@ -86,16 +86,17 @@ class Auth { return cb(null, JSON.stringify({ "username": user.username, "id": user.id, + "email": user.email, })) }) }) // define how to deseralize a user (use the username to get it from the database) passport.deserializeUser((function (user, cb) { - process.nextTick((function () { + process.nextTick((async function () { const parsedUserInfo = JSON.parse(user) // TODO: do the matching on username or better on id? - const dbUser = this.db.users.find(u => u.username.toLowerCase() === parsedUserInfo.username.toLowerCase()) + const dbUser = await Database.userModel.getUserByUsername(parsedUserInfo.username.toLowerCase()) return cb(null, dbUser) }).bind(this)) }).bind(this)) @@ -108,9 +109,9 @@ class Auth { initAuthRoutes(router) { // Local strategy login route (takes username and password) router.post('/login', passport.authenticate('local'), - (function (req, res) { + (async function (req, res) { // return the user login response json if the login was successfull - res.json(this.getUserLoginResponsePayload(req.user)) + res.json(await this.getUserLoginResponsePayload(req.user)) }).bind(this) ) @@ -120,9 +121,12 @@ class Auth { // google-oauth20 strategy callback route (this receives the token from google) router.get('/auth/google/callback', passport.authenticate('google'), - (function (req, res) { + (async function (req, res) { // return the user login response json if the login was successfull - res.json(this.getUserLoginResponsePayload(req.user)) + var data_json = await this.getUserLoginResponsePayload(req.user) + // res.json(data_json) + // TODO: figure out how to redirect back to the app page + res.redirect(301, `http://localhost:3000/login?setToken=${data_json.user.token}`) }).bind(this) ) @@ -132,9 +136,12 @@ class Auth { // openid strategy callback route (this receives the token from the configured openid login provider) router.get('/auth/openid/callback', passport.authenticate('openidconnect'), - (function (req, res) { + (async function (req, res) { // return the user login response json if the login was successfull - res.json(this.getUserLoginResponsePayload(req.user)) + var data_json = await this.getUserLoginResponsePayload(req.user) + // res.json(data_json) + // TODO: figure out how to redirect back to the app page + res.redirect(301, `http://localhost:3000/login?setToken=${data_json.user.token}`) }).bind(this) ) @@ -176,6 +183,20 @@ class Auth { return jwt.sign({ userId: user.id, username: user.username }, global.ServerSettings.tokenSecret) } + /** + * Function to generate a jwt token for a given user. + * @param {string} token + * @returns the tokens data. + */ + static validateAccessToken(token) { + try { + return jwt.verify(token, global.ServerSettings.tokenSecret) + } + catch (err) { + return null + } + } + /** * Generate a token for each user. */ @@ -203,7 +224,7 @@ class Auth { * @param {function} done */ jwtAuthCheck(jwt_payload, done) { - const user = this.db.users.find(u => u.username.toLowerCase() === jwt_payload.username.toLowerCase()) + const user = Database.userModel.getUserByUsername(jwt_payload.username.toLowerCase()) if (!user || !user.isActive) { done(null, null) @@ -220,7 +241,7 @@ class Auth { * @param {function} done */ async localAuthCheckUserPw(username, password, done) { - const user = this.db.users.find(u => u.username.toLowerCase() === username.toLowerCase()) + const user = Database.userModel.getUserByUsername(username.toLowerCase()) if (!user || !user.isActive) { done(null, null) @@ -269,7 +290,8 @@ class Auth { * @param {string} username * @returns {string} jsonPayload */ - getUserLoginResponsePayload(user) { + async getUserLoginResponsePayload(user) { + const libraryIds = await Database.libraryModel.getAllLibraryIds() return { user: user.toJSONForBrowser(), userDefaultLibraryId: user.getDefaultLibraryId(libraryIds), diff --git a/server/Server.js b/server/Server.js index 9156c021..89985768 100644 --- a/server/Server.js +++ b/server/Server.js @@ -161,7 +161,8 @@ class Server { this.server = http.createServer(app) - router.use(this.auth.cors) + + router.use(fileUpload({ defCharset: 'utf8', defParamCharset: 'utf8', @@ -195,6 +196,9 @@ class Server { this.rssFeedManager.getFeedItem(req, res) }) + // Auth routes + this.auth.initAuthRoutes(router) + // Client dynamic routes const dyanimicRoutes = [ '/item/:id', diff --git a/server/SocketAuthority.js b/server/SocketAuthority.js index 5c3c8715..81136ecf 100644 --- a/server/SocketAuthority.js +++ b/server/SocketAuthority.js @@ -1,6 +1,9 @@ const SocketIO = require('socket.io') const Logger = require('./Logger') const Database = require('./Database') +const Auth = require('./Auth') +const passport = require('passport') +const expressSession = require('express-session') class SocketAuthority { constructor() { @@ -81,6 +84,24 @@ class SocketAuthority { methods: ["GET", "POST"] } }) + + /* + const wrap = middleware => (socket, next) => middleware(socket.request, {}, next); + + io.use(wrap(expressSession({ + secret: global.ServerSettings.tokenSecret, + resave: false, + saveUninitialized: false, + cookie: { + // also send the cookie if were hare not on https + secure: false + }, + }))); + + io.use(wrap(passport.initialize())); + io.use(wrap(passport.session())); + */ + this.io.on('connection', (socket) => { this.clients[socket.id] = { id: socket.id, @@ -147,7 +168,13 @@ class SocketAuthority { // When setting up a socket connection the user needs to be associated with a socket id // for this the client will send a 'auth' event that includes the users API token async authenticateSocket(socket, token) { - const user = await this.Server.db.users.find(u => u.token === token) + // TODO + const token_data = Auth.validateAccessToken(token) + if (!token_data || !token_data.username) { + Logger.error('Cannot validate socket - invalid token') + return socket.emit('invalid_token') + } + const user = await Database.userModel.getUserByUsername(token_data.username) if (!user) { Logger.error('Cannot validate socket - invalid token') return socket.emit('invalid_token') diff --git a/server/models/User.js b/server/models/User.js index 6f457aa5..5e184fc7 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -194,6 +194,25 @@ class User extends Model { return this.getOldUser(user) } + /** + * Get user by email case insensitive + * @param {string} username + * @returns {Promise} returns null if not found + */ + static async getUserByEmail(email) { + if (!email) return null + const user = await this.findOne({ + where: { + email: { + [Op.like]: email + } + }, + include: this.sequelize.models.mediaProgress + }) + if (!user) return null + return this.getOldUser(user) + } + /** * Get user by id * @param {string} userId