@adonisjs/auth
Version:
Official authentication provider for Adonis framework
339 lines (338 loc) • 11.2 kB
JavaScript
;
/*
* @adonisjs/auth
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = require("path");
const helpers_1 = require("@poppinss/utils/build/helpers");
// const USER_MIGRATION_TIME_PREFIX = '1587988332388'
// const TOKENS_MIGRATION_TIME_PREFIX = '1592489784670'
/**
* Base path to contract stub partials
*/
const CONTRACTS_PARTIALS_BASE = './contract/partials';
/**
* Base path to config stub partials
*/
const CONFIG_PARTIALS_BASE = './config/partials';
/**
* Prompt choices for the provider selection
*/
const PROVIDER_PROMPT_CHOICES = [
{
name: 'lucid',
message: 'Lucid',
hint: ' (Uses Data Models)',
},
{
name: 'database',
message: 'Database',
hint: ' (Uses Database QueryBuilder)',
},
];
/**
* Prompt choices for the guard selection
*/
const GUARD_PROMPT_CHOICES = [
{
name: 'web',
message: 'Web',
hint: ' (Uses sessions for managing auth state)',
},
{
name: 'api',
message: 'API tokens',
hint: ' (Uses database backed opaque tokens)',
},
{
name: 'basic',
message: 'Basic Auth',
hint: ' (Uses HTTP Basic auth for authenticating requests)',
},
];
/**
* Prompt choices for the tokens provider selection
*/
const TOKENS_PROVIDER_PROMPT_CHOICES = [
{
name: 'database',
message: 'Database',
hint: ' (Uses SQL table for storing API tokens)',
},
{
name: 'redis',
message: 'Redis',
hint: ' (Uses Redis for storing API tokens)',
},
];
/**
* Returns absolute path to the stub relative from the templates
* directory
*/
function getStub(...relativePaths) {
return (0, path_1.join)(__dirname, 'templates', ...relativePaths);
}
/**
* Creates the model file
*/
function makeModel(projectRoot, app, sink, state) {
const modelsDirectory = app.resolveNamespaceDirectory('models') || 'app/Models';
const modelPath = (0, path_1.join)(modelsDirectory, `${state.modelName}.ts`);
const template = new sink.files.MustacheFile(projectRoot, modelPath, getStub('model.txt'));
if (template.exists()) {
sink.logger.action('create').skipped(`${modelPath} file already exists`);
return;
}
template.apply(state).commit();
sink.logger.action('create').succeeded(modelPath);
}
/**
* Create the migration file
*/
function makeUsersMigration(projectRoot, app, sink, state) {
const migrationsDirectory = app.directoriesMap.get('migrations') || 'database';
const migrationPath = (0, path_1.join)(migrationsDirectory, `${Date.now()}_${state.usersTableName}.ts`);
const template = new sink.files.MustacheFile(projectRoot, migrationPath, getStub('migrations/auth.txt'));
if (template.exists()) {
sink.logger.action('create').skipped(`${migrationPath} file already exists`);
return;
}
template.apply(state).commit();
sink.logger.action('create').succeeded(migrationPath);
}
/**
* Create the migration file
*/
function makeTokensMigration(projectRoot, app, sink, state) {
const migrationsDirectory = app.directoriesMap.get('migrations') || 'database';
const migrationPath = (0, path_1.join)(migrationsDirectory, `${Date.now()}_${state.tokensTableName}.ts`);
const template = new sink.files.MustacheFile(projectRoot, migrationPath, getStub('migrations/api_tokens.txt'));
if (template.exists()) {
sink.logger.action('create').skipped(`${migrationPath} file already exists`);
return;
}
template.apply(state).commit();
sink.logger.action('create').succeeded(migrationPath);
}
/**
* Create the middleware(s)
*/
function makeMiddleware(projectRoot, app, sink, state) {
const middlewareDirectory = app.resolveNamespaceDirectory('middleware') || 'app/Middleware';
/**
* Auth middleware
*/
const authPath = (0, path_1.join)(middlewareDirectory, 'Auth.ts');
const authTemplate = new sink.files.MustacheFile(projectRoot, authPath, getStub('middleware/Auth.txt'));
if (authTemplate.exists()) {
sink.logger.action('create').skipped(`${authPath} file already exists`);
}
else {
authTemplate.apply(state).commit();
sink.logger.action('create').succeeded(authPath);
}
/**
* Silent auth middleware
*/
const silentAuthPath = (0, path_1.join)(middlewareDirectory, 'SilentAuth.ts');
const silentAuthTemplate = new sink.files.MustacheFile(projectRoot, silentAuthPath, getStub('middleware/SilentAuth.txt'));
if (silentAuthTemplate.exists()) {
sink.logger.action('create').skipped(`${silentAuthPath} file already exists`);
}
else {
silentAuthTemplate.apply(state).commit();
sink.logger.action('create').succeeded(silentAuthPath);
}
}
/**
* Creates the contract file
*/
function makeContract(projectRoot, app, sink, state) {
const contractsDirectory = app.directoriesMap.get('contracts') || 'contracts';
const contractPath = (0, path_1.join)(contractsDirectory, 'auth.ts');
const template = new sink.files.MustacheFile(projectRoot, contractPath, getStub('contract/auth.txt'));
template.overwrite = true;
const partials = {
provider: getStub(CONTRACTS_PARTIALS_BASE, `user-provider-${state.provider}.txt`),
};
state.guards.forEach((guard) => {
partials[`${guard}_guard`] = getStub(CONTRACTS_PARTIALS_BASE, `${guard}-guard.txt`);
});
template.apply(state).partials(partials).commit();
sink.logger.action('create').succeeded(contractPath);
}
/**
* Makes the auth config file
*/
function makeConfig(projectRoot, app, sink, state) {
const configDirectory = app.directoriesMap.get('config') || 'config';
const configPath = (0, path_1.join)(configDirectory, 'auth.ts');
const template = new sink.files.MustacheFile(projectRoot, configPath, getStub('config/auth.txt'));
template.overwrite = true;
const partials = {
provider: getStub(CONFIG_PARTIALS_BASE, `user-provider-${state.provider}.txt`),
token_provider: getStub(CONFIG_PARTIALS_BASE, `tokens-provider-${state.tokensProvider}.txt`),
};
state.guards.forEach((guard) => {
partials[`${guard}_guard`] = getStub(CONFIG_PARTIALS_BASE, `${guard}-guard.txt`);
});
template.apply(state).partials(partials).commit();
sink.logger.action('create').succeeded(configPath);
}
/**
* Prompts user to select the provider
*/
async function getProvider(sink) {
return sink.getPrompt().choice('Select provider for finding users', PROVIDER_PROMPT_CHOICES, {
validate(choice) {
return choice && choice.length ? true : 'Select the provider for finding users';
},
});
}
/**
* Prompts user to select the tokens provider
*/
async function getTokensProvider(sink) {
return sink
.getPrompt()
.choice('Select the provider for storing API tokens', TOKENS_PROVIDER_PROMPT_CHOICES, {
validate(choice) {
return choice && choice.length ? true : 'Select the provider for storing API tokens';
},
});
}
/**
* Prompts user to select one or more guards
*/
async function getGuard(sink) {
return sink
.getPrompt()
.multiple('Select which guard you need for authentication (select using space)', GUARD_PROMPT_CHOICES, {
validate(choices) {
return choices && choices.length
? true
: 'Select one or more guards for authenticating users';
},
});
}
/**
* Prompts user for the model name
*/
async function getModelName(sink) {
return sink.getPrompt().ask('Enter model name to be used for authentication', {
validate(value) {
return !!value.trim().length;
},
});
}
/**
* Prompts user for the table name
*/
async function getTableName(sink) {
return sink.getPrompt().ask('Enter the database table name to look up users', {
validate(value) {
return !!value.trim().length;
},
});
}
/**
* Prompts user for the table name
*/
async function getMigrationConsent(sink, tableName) {
return sink
.getPrompt()
.confirm(`Create migration for the ${sink.logger.colors.underline(tableName)} table?`);
}
/**
* Instructions to be executed when setting up the package.
*/
async function instructions(projectRoot, app, sink) {
const state = {
usersTableName: '',
tokensTableName: 'api_tokens',
tokensSchemaName: 'ApiTokens',
usersSchemaName: '',
provider: 'lucid',
tokensProvider: 'database',
guards: [],
hasGuard: {
web: false,
api: false,
basic: false,
},
};
state.provider = await getProvider(sink);
state.guards = await getGuard(sink);
/**
* Need booleans for mustache templates
*/
state.guards.forEach((guard) => (state.hasGuard[guard] = true));
/**
* Make model when provider is lucid otherwise prompt for the database
* table name
*/
if (state.provider === 'lucid') {
const modelName = await getModelName(sink);
state.modelName = modelName.replace(/(\.ts|\.js)$/, '');
state.usersTableName = helpers_1.string.pluralize(helpers_1.string.snakeCase(state.modelName));
state.modelReference = helpers_1.string.camelCase(state.modelName);
state.modelNamespace = `${app.namespacesMap.get('models') || 'App/Models'}/${state.modelName}`;
}
else {
state.usersTableName = await getTableName(sink);
}
const usersMigrationConsent = await getMigrationConsent(sink, state.usersTableName);
let tokensMigrationConsent = false;
/**
* Only ask for the consent when using the api guard
*/
if (state.hasGuard.api) {
state.tokensProvider = await getTokensProvider(sink);
if (state.tokensProvider === 'database') {
tokensMigrationConsent = await getMigrationConsent(sink, state.tokensTableName);
}
}
/**
* Pascal case
*/
const camelCaseSchemaName = helpers_1.string.camelCase(`${state.usersTableName}_schema`);
state.usersSchemaName = `${camelCaseSchemaName
.charAt(0)
.toUpperCase()}${camelCaseSchemaName.slice(1)}`;
/**
* Make model when prompted for it
*/
if (state.modelName) {
makeModel(projectRoot, app, sink, state);
}
/**
* Make users migration file
*/
if (usersMigrationConsent) {
makeUsersMigration(projectRoot, app, sink, state);
}
/**
* Make tokens migration file
*/
if (tokensMigrationConsent) {
makeTokensMigration(projectRoot, app, sink, state);
}
/**
* Make contract file
*/
makeContract(projectRoot, app, sink, state);
/**
* Make config file
*/
makeConfig(projectRoot, app, sink, state);
/**
* Make middleware
*/
makeMiddleware(projectRoot, app, sink, state);
}
exports.default = instructions;