mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-11-04 03:17:00 -05:00 
			
		
		
		
	Update /status endpoint to return available auth methods, fix socket auth, update openid to use username instead of email
This commit is contained in:
		
							parent
							
								
									9922294507
								
							
						
					
					
						commit
						f6de373388
					
				@ -25,8 +25,11 @@
 | 
			
		||||
      </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 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" />
 | 
			
		||||
@ -37,7 +40,9 @@
 | 
			
		||||
            <ui-btn type="submit" :disabled="processing" color="primary" class="leading-none">{{ processing ? 'Checking...' : $strings.ButtonSubmit }}</ui-btn>
 | 
			
		||||
          </div>
 | 
			
		||||
        </form>
 | 
			
		||||
        <hr />
 | 
			
		||||
 | 
			
		||||
        <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="`http://localhost:3333/auth/google?callback=${currentUrl}`">
 | 
			
		||||
            <ui-btn color="primary" class="leading-none">Login with Google</ui-btn>
 | 
			
		||||
@ -106,6 +111,9 @@ export default {
 | 
			
		||||
  computed: {
 | 
			
		||||
    user() {
 | 
			
		||||
      return this.$store.state.user.user
 | 
			
		||||
    },
 | 
			
		||||
    googleAuthUri() {
 | 
			
		||||
      return `${process.env.serverUrl}/auth/openid?callback=${currentUrl}`
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
@ -210,14 +218,16 @@ 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) => {
 | 
			
		||||
@ -226,43 +236,34 @@ export default {
 | 
			
		||||
          this.criticalError = 'Status check failed'
 | 
			
		||||
        })
 | 
			
		||||
    },
 | 
			
		||||
    async updateLoginVisibility() {
 | 
			
		||||
      await this.$axios
 | 
			
		||||
        .$get('/auth_methods')
 | 
			
		||||
        .then((response) => {
 | 
			
		||||
          if (response.includes('local')) {
 | 
			
		||||
    updateLoginVisibility(authMethods) {
 | 
			
		||||
      if (authMethods.includes('local') || !authMethods.length) {
 | 
			
		||||
        this.login_local = true
 | 
			
		||||
      } else {
 | 
			
		||||
        this.login_local = false
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
          if (response.includes('google-oauth20')) {
 | 
			
		||||
      if (authMethods.includes('google-oauth20')) {
 | 
			
		||||
        this.login_google_oauth20 = true
 | 
			
		||||
      } else {
 | 
			
		||||
        this.login_google_oauth20 = false
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
          if (response.includes('openid')) {
 | 
			
		||||
      if (authMethods.includes('openid')) {
 | 
			
		||||
        this.login_openid = true
 | 
			
		||||
      } else {
 | 
			
		||||
        this.login_openid = false
 | 
			
		||||
      }
 | 
			
		||||
        })
 | 
			
		||||
        .catch((error) => {
 | 
			
		||||
          console.error('Failed', error.response)
 | 
			
		||||
          return false
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  async mounted() {
 | 
			
		||||
    this.$nextTick(async () => await this.updateLoginVisibility())
 | 
			
		||||
    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()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -64,10 +64,9 @@ class Auth {
 | 
			
		||||
        (async function (issuer, profile, done) {
 | 
			
		||||
          // TODO: do we want to create the users which does not exist?
 | 
			
		||||
 | 
			
		||||
          // get user by email
 | 
			
		||||
          var user = await Database.userModel.getUserByEmail(profile.emails[0].value.toLowerCase())
 | 
			
		||||
          const user = await Database.userModel.getUserByUsername(profile.username)
 | 
			
		||||
 | 
			
		||||
          if (!user || !user.isActive) {
 | 
			
		||||
          if (!user?.isActive) {
 | 
			
		||||
            // deny login
 | 
			
		||||
            done(null, null)
 | 
			
		||||
            return
 | 
			
		||||
@ -106,9 +105,10 @@ class Auth {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Stores the client's choise how the login callback should happen in temp cookies.
 | 
			
		||||
   * @param {*} req Request object.
 | 
			
		||||
   * @param {*} res Response object.
 | 
			
		||||
   * Stores the client's choice how the login callback should happen in temp cookies
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {import('express').Request} req
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
   */
 | 
			
		||||
  paramsToCookies(req, res) {
 | 
			
		||||
    if (req.query.isRest && req.query.isRest.toLowerCase() == "true") {
 | 
			
		||||
@ -140,12 +140,12 @@ class Auth {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Informs the client in the right mode about a successfull login and the token
 | 
			
		||||
   * (clients choise is restored from cookies).
 | 
			
		||||
   * @param {*} req Request object.
 | 
			
		||||
   * @param {*} res Response object.
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {import('express').Request} req
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async handleLoginSuccessBasedOnCookie(req, res) {
 | 
			
		||||
    // get userLogin json (information about the user, server and the session)
 | 
			
		||||
@ -170,16 +170,15 @@ class Auth {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates all (express) routes required for authentication.
 | 
			
		||||
   * @param {express.Router} router 
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {import('express').Router} router 
 | 
			
		||||
   */
 | 
			
		||||
  async initAuthRoutes(router) {
 | 
			
		||||
    // Local strategy login route (takes username and password)
 | 
			
		||||
    router.post('/login', passport.authenticate('local'),
 | 
			
		||||
      (async function (req, res) {
 | 
			
		||||
    router.post('/login', passport.authenticate('local'), async (req, res) => {
 | 
			
		||||
      // return the user login response json if the login was successfull
 | 
			
		||||
      res.json(await this.getUserLoginResponsePayload(req.user))
 | 
			
		||||
      }).bind(this)
 | 
			
		||||
    )
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    // google-oauth20 strategy login route (this redirects to the google login)
 | 
			
		||||
    router.get('/auth/google', (req, res, next) => {
 | 
			
		||||
@ -222,18 +221,13 @@ class Auth {
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    // Get avilible auth methods
 | 
			
		||||
    router.get('/auth_methods', (req, res) => {
 | 
			
		||||
      res.json(global.ServerSettings.authActiveAuthMethods)
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * middleware to use in express to only allow authenticated users.
 | 
			
		||||
   * @param {express.Request} req 
 | 
			
		||||
   * @param {express.Response} res 
 | 
			
		||||
   * @param {express.NextFunction} next  
 | 
			
		||||
   * @param {import('express').Request} req 
 | 
			
		||||
   * @param {import('express').Response} res 
 | 
			
		||||
   * @param {import('express').NextFunction} next  
 | 
			
		||||
   */
 | 
			
		||||
  isAuthenticated(req, res, next) {
 | 
			
		||||
    // check if session cookie says that we are authenticated
 | 
			
		||||
@ -246,18 +240,20 @@ class Auth {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Function to generate a jwt token for a given user.
 | 
			
		||||
   * Function to generate a jwt token for a given user
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {Object} user 
 | 
			
		||||
   * @returns the token.
 | 
			
		||||
   * @returns {string} token
 | 
			
		||||
   */
 | 
			
		||||
  generateAccessToken(user) {
 | 
			
		||||
    return jwt.sign({ userId: user.id, username: user.username }, global.ServerSettings.tokenSecret)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Function to validate a jwt token for a given user.
 | 
			
		||||
   * Function to validate a jwt token for a given user
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} token 
 | 
			
		||||
   * @returns the tokens data.
 | 
			
		||||
   * @returns {Object} tokens data
 | 
			
		||||
   */
 | 
			
		||||
  static validateAccessToken(token) {
 | 
			
		||||
    try {
 | 
			
		||||
@ -365,9 +361,10 @@ class Auth {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Return the login info payload for a user.
 | 
			
		||||
   * @param {string} username 
 | 
			
		||||
   * @returns {Promise<string>} jsonPayload
 | 
			
		||||
   * Return the login info payload for a user
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {Object} user 
 | 
			
		||||
   * @returns {Promise<Object>} jsonPayload
 | 
			
		||||
   */
 | 
			
		||||
  async getUserLoginResponsePayload(user) {
 | 
			
		||||
    const libraryIds = await Database.libraryModel.getAllLibraryIds()
 | 
			
		||||
 | 
			
		||||
@ -238,7 +238,8 @@ class Server {
 | 
			
		||||
      // server has been initialized if a root user exists
 | 
			
		||||
      const payload = {
 | 
			
		||||
        isInit: Database.hasRootUser,
 | 
			
		||||
        language: Database.serverSettings.language
 | 
			
		||||
        language: Database.serverSettings.language,
 | 
			
		||||
        authMethods: Database.serverSettings.authActiveAuthMethods
 | 
			
		||||
      }
 | 
			
		||||
      if (!payload.isInit) {
 | 
			
		||||
        payload.ConfigPath = global.ConfigPath
 | 
			
		||||
 | 
			
		||||
@ -146,24 +146,31 @@ 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
 | 
			
		||||
  /**
 | 
			
		||||
   * 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
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {SocketIO.Socket} socket 
 | 
			
		||||
   * @param {string} token JWT
 | 
			
		||||
   */
 | 
			
		||||
  async authenticateSocket(socket, token) {
 | 
			
		||||
    // we don't use passport to authenticate the jwt we get over the socket connection.
 | 
			
		||||
    // it's easier to directly verify/decode it.
 | 
			
		||||
    const token_data = Auth.validateAccessToken(token)
 | 
			
		||||
    if (!token_data || !token_data.id) {
 | 
			
		||||
 | 
			
		||||
    if (!token_data?.userId) {
 | 
			
		||||
      // Token invalid
 | 
			
		||||
      Logger.error('Cannot validate socket - invalid token')
 | 
			
		||||
      return socket.emit('invalid_token')
 | 
			
		||||
    }
 | 
			
		||||
    // get the user via the id from the decoded jwt.
 | 
			
		||||
    const user = await Database.userModel.getUserById(token_data.id)
 | 
			
		||||
    const user = await Database.userModel.getUserByIdOrOldId(token_data.userId)
 | 
			
		||||
    if (!user) {
 | 
			
		||||
      // user not found
 | 
			
		||||
      Logger.error('Cannot validate socket - invalid token')
 | 
			
		||||
      return socket.emit('invalid_token')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const client = this.clients[socket.id]
 | 
			
		||||
    if (!client) {
 | 
			
		||||
      Logger.error(`[SocketAuthority] Socket for user ${user.username} has no client`)
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user