feathersjs / feathers

The API and real-time application framework
https://feathersjs.com
MIT License
15.06k stars 752 forks source link

how to attach authentication strategy and user object in context #2326

Closed hhchow7 closed 3 years ago

hhchow7 commented 3 years ago

Every time I login, below body is passed:

POST /authentication

{
    "strategy": "admin",
    "username": "{{adminUsername}}",
    "password": "{{adminPassword}}"
}

And then return:

{
    "accessToken": "xxxxxxxx",
    "authentication": {
        "strategy": "admin"
    },
    "user": {
        "id": "xxxx",
        "username": "cccc",
        "email": "cccc@company.com",
    }
}

I want to add below json in the context object:

{
    "user": {
        "id": "xxxx",
        "username": "cccc",
        "email": "cccc@company.com",
        "strategy": "admin".   <---------How to add this
    }
}

How to do it?

const { AuthenticationService, AuthenticationBaseStrategy, JWTStrategy } = require('@feathersjs/authentication');
const { LocalStrategy } = require('@feathersjs/authentication-local');
const { expressOauth } = require('@feathersjs/authentication-oauth');

const uuidv4 = require('uuid/v4');
const { omit } = require('lodash');

class SessionAuthService extends AuthenticationService {

  constructor(app, configKey) {
    super(app, configKey);

    app.on('logout', async (authResult, params, context) => {
      await app.service(this.configuration.service).patch(authResult.user.id, { session: null }).catch(e => null);
      await app.service(this.configuration.adminService).patch(authResult.user.id, { session: null }).catch(e => null);
  }

  async getPayload(authResult, params) {
    const service = 
                    (authResult.authentication.strategy == 'admin' ? this.configuration.adminService : 
                    this.configuration.service) );
    const payload = await super.getPayload(authResult, params);
    const user = await this.app.service(service).patch(authResult.user.id, { session: uuidv4(), lastLoginTime: Date.now()});
    return {
      ...payload,
      ses: user.session,
    };
  }

  async verifyAccessToken(accessToken, options, secret) {
    const verifyResult = await super.verifyAccessToken(accessToken, options, secret);
    const user = await this.app.service(this.configuration.service).get(verifyResult.sub).catch(e => null);
    const admin = await this.app.service(this.configuration.adminService).get(verifyResult.sub).catch(e => null);

    if (user || admin) {
      const { session } = user || admin;
      if (verifyResult.ses != session) {
        throw new errors.NotAuthenticated('token revoked');
      }
    }
    return verifyResult;
  }

}

class JWTWithAdminStrategy extends JWTStrategy {
  async getEntity(id, params) {

    const { entity } = this.configuration;
    const entityService = this.entityService;

    if (entityService === null) {
      throw new NotAuthenticated(`Could not find entity service`);
    }

    const user = await entityService.get(id, omit(params, 'provider')).catch(e => null);

    const admin = await this.app.service(this.authentication.configuration.adminService).get(id, omit(params, 'provider')).catch(e => null);

    const result = user || admin
    const service = user ? entityService : 
                    (admin? this.app.service(this.authentication.configuration.adminService) :
"");

    return service.get(id, { ...params, [entity]: result });
  }

}

class AdminStrategy extends LocalStrategy {
  get configuration () {
    const authConfig = this.authentication.configuration;
    const config = super.configuration || {};

    return {
      hashSize: 10,
      service: config.service,
      entity: authConfig.entity,
      entityId: authConfig.entityId,
      errorMessage: 'Invalid login',
      entityPasswordField: config.passwordField,
      entityUsernameField: config.usernameField,
      ...config
    };
  }
  async getEntity(result, params) {
    const entityInfo = await super.getEntity(result, params);
    if (!entityInfo.enabled) {
      throw new errors.NotAuthenticated('disabled');
    }
    return entityInfo;
  }
}

module.exports = app => {
  const authentication = new SessionAuthService(app);

  authentication.register('jwt', new JWTWithAdminStrategy());
  authentication.register('admin', new AdminStrategy());

  app.use('/authentication', authentication);
  app.configure(expressOauth());
};
StevenMalaihollo commented 3 years ago

There are multiple options:

I see you don't use hooks in this authentication service, you could add an after hook to the Create path for this Authentication service. You could also overrite the entire create method to return an adjusted output.

Customizing the create hook: https://docs.feathersjs.com/api/authentication/service.html#customization