Init auth

This commit is contained in:
Alexandre Bodin 2021-09-06 19:58:59 +02:00
parent 3b41a627a4
commit 7aa012da32
11 changed files with 225 additions and 57 deletions

View File

@ -18,6 +18,7 @@ const { createCoreStore, coreStoreModel } = require('./services/core-store');
const createEntityService = require('./services/entity-service');
const entityValidator = require('./services/entity-validator');
const createTelemetry = require('./services/metrics');
const createContentAPI = require('./services/content-api');
const createUpdateNotifier = require('./utils/update-notifier');
const createStartupLogger = require('./utils/startup-logger');
const ee = require('./utils/ee');
@ -52,6 +53,7 @@ class Strapi {
this.container.register('modules', modulesRegistry(this));
this.container.register('plugins', pluginsRegistry(this));
this.container.register('apis', apisRegistry(this));
this.container.register('content-api', createContentAPI(this));
this.isLoaded = false;
this.reload = this.reload();
@ -388,7 +390,7 @@ class Strapi {
await execLifecycle(this.config.get(configPath));
// admin
await this.admin[lifecycleName]();
await this.admin[lifecycleName](this);
}
getModel(uid) {

View File

@ -1,6 +1,7 @@
'use strict';
const _ = require('lodash');
const { toLower } = require('lodash/fp');
module.exports = strapi => {
const registerAdminRoutes = () => {
@ -52,6 +53,15 @@ module.exports = strapi => {
// pass meta down to compose endpoint
router.type = 'content-api';
router.routes.forEach(route => {
if (typeof route.handler === 'string') {
const [controller, action] = route.handler.split('.');
_.defaultsDeep(route.config, {
auth: {
scope: `application.${toLower(controller)}.${toLower(action)}`,
},
});
}
route.info = { apiName };
});

View File

@ -0,0 +1,53 @@
'use strict';
const { strict: assert } = require('assert');
const { has } = require('lodash/fp');
const INVALID_STRATEGY_MSG =
'Invalid auth strategy. Expecting an object with properties {name: string, authenticate: function}';
const validStrategy = strategy => {
assert(has('name', strategy), INVALID_STRATEGY_MSG);
assert(typeof strategy.name === 'string', INVALID_STRATEGY_MSG);
assert(has('authenticate', strategy), INVALID_STRATEGY_MSG);
assert(typeof strategy.authenticate === 'function', INVALID_STRATEGY_MSG);
};
const createAuthentication = () => {
const strategies = [];
return {
register(strategy) {
validStrategy(strategy);
strategies.push(strategy);
return () => {
strategies.splice(strategies.indexOf(strategy), 1);
};
},
async authenticate(ctx) {
for (const strategy of strategies) {
const result = await strategy.authenticate(ctx);
const { authenticated = false, credentials, scope } = result || {};
if (authenticated) {
ctx.state.auth = {
isAuthenticated: authenticated,
scope,
credentials,
};
return;
}
}
},
};
};
module.exports = () => {
return {
auth: createAuthentication(),
};
};

View File

@ -28,6 +28,10 @@ const authPolicy = (ctx, next) => {
}
if (!has('auth.scope', config)) {
return ctx.unauthorized();
}
if (config.auth.scope === '*') {
// just requires authentication
return next();
}
@ -52,10 +56,8 @@ const createContentAPI = strapi => {
const api = createAPI(strapi, opts);
// implement auth providers
api.use((ctx, next) => {
ctx.state.auth = {
isAuthenticated: true,
};
api.use(async (ctx, next) => {
await strapi.container.get('content-api').auth.authenticate(ctx);
return next();
});

View File

@ -44,8 +44,8 @@ const createServer = strapi => {
const httpServer = createHTTPServer(strapi, app);
const apis = {
admin: createAdminAPI(strapi),
'content-api': createContentAPI(strapi),
admin: createAdminAPI(strapi),
};
// init health check

View File

@ -63,8 +63,7 @@ const validateRouteConfig = routeConfig => {
stripUnknown: true,
});
} catch (error) {
console.error(error);
throw new Error('Invalid route config');
throw new Error('Invalid route config', error.message);
}
};

View File

@ -0,0 +1,23 @@
'use strict';
const register = require('./register');
const bootstrap = require('./bootstrap');
const contentTypes = require('./content-types');
const policies = require('./policies');
const services = require('./services');
const routes = require('./routes');
const controllers = require('./controllers');
const middlewares = require('./middlewares');
const config = require('./config');
module.exports = () => ({
register,
bootstrap,
config,
routes,
controllers,
middlewares,
contentTypes,
policies,
services,
});

View File

@ -0,0 +1,92 @@
'use strict';
const { map } = require('lodash/fp');
const { getService } = require('./utils');
const getAdvancedSettings = () => {
return strapi
.store({
environment: '',
type: 'plugin',
name: 'users-permissions',
})
.get({ key: 'advanced' });
};
const permissionToScope = permission => {
// missing apiName type
return `${permission.type}.${permission.controller}.${permission.action}`;
};
module.exports = strapi => {
strapi.container.get('content-api').auth.register({
name: 'users-permissions',
async authenticate(ctx) {
if (ctx.request && ctx.request.header && ctx.request.header.authorization) {
try {
const { id } = await getService('jwt').getToken(ctx);
if (id === undefined) {
throw new Error('Invalid token: Token did not contain required fields');
}
// fetch authenticated user
const user = await getService('user').fetchAuthenticatedUser(id);
if (user) {
const permissions = await strapi
.query('plugin::users-permissions.permission')
.findMany({
where: {
role: user.role.id,
enabled: true,
},
});
return {
authenticated: true,
credentials: user,
scope: map(permissionToScope, permissions),
};
}
} catch (err) {
return { authenticated: false };
}
if (!ctx.state.user) {
return { authenticated: false };
}
const advancedSettings = await getAdvancedSettings();
if (advancedSettings.email_confirmation && !ctx.state.user.confirmed) {
return { authenticated: false };
}
if (ctx.state.user.blocked) {
return { authenticated: false };
}
}
const publicPermissions = await strapi
.query('plugin::users-permissions.permission')
.findMany({
where: {
role: { type: 'public' },
enabled: true,
},
});
if (publicPermissions.length === 0) {
return { authenticated: false };
}
return {
authenticated: true,
credentials: null,
scope: map(permissionToScope, publicPermissions),
};
},
});
};

View File

@ -202,24 +202,24 @@ module.exports = {
prefix: '',
},
},
{
method: 'POST',
path: '/auth/local',
handler: 'auth.callback',
config: {
policies: ['plugin::users-permissions.rateLimit'],
prefix: '',
},
},
{
method: 'POST',
path: '/auth/local/register',
handler: 'auth.register',
config: {
policies: ['plugin::users-permissions.rateLimit'],
prefix: '',
},
},
// {
// method: 'POST',
// path: '/auth/local',
// handler: 'auth.callback',
// config: {
// policies: ['plugin::users-permissions.rateLimit'],
// prefix: '',
// },
// },
// {
// method: 'POST',
// path: '/auth/local/register',
// handler: 'auth.register',
// config: {
// policies: ['plugin::users-permissions.rateLimit'],
// prefix: '',
// },
// },
{
method: 'GET',
path: '/auth/:provider/callback',

View File

@ -2,22 +2,27 @@
module.exports = {
type: 'content-api',
// TODO:
routes: [
{
method: 'GET',
path: '/users/count',
handler: 'user.count',
method: 'POST',
path: '/auth/local',
handler: 'auth.callback',
config: {
auth: { public: true },
policies: ['plugin::users-permissions.rateLimit'],
prefix: '',
},
},
{
method: 'GET',
path: '/users',
handler: 'user.find',
},
{
method: 'GET',
path: '/users/me',
handler: 'user.me',
method: 'POST',
path: '/auth/local/register',
handler: 'auth.register',
config: {
auth: { public: true },
policies: ['plugin::users-permissions.rateLimit'],
prefix: '',
},
},
],
// TODO: add connection / auto registration routes
};

View File

@ -1,21 +1,3 @@
'use strict';
const bootstrap = require('./server/bootstrap');
const contentTypes = require('./server/content-types');
const policies = require('./server/policies');
const services = require('./server/services');
const routes = require('./server/routes');
const controllers = require('./server/controllers');
const middlewares = require('./server/middlewares');
const config = require('./server/config');
module.exports = () => ({
bootstrap,
config,
routes,
controllers,
middlewares,
contentTypes,
policies,
services,
});
module.exports = require('./server');