UNPKG

11.2 kBJavaScriptView Raw
1"use strict";
2/*
3 * @adonisjs/auth
4 *
5 * (c) Harminder Virk <virk@adonisjs.com>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10Object.defineProperty(exports, "__esModule", { value: true });
11const path_1 = require("path");
12const helpers_1 = require("@poppinss/utils/build/helpers");
13// const USER_MIGRATION_TIME_PREFIX = '1587988332388'
14// const TOKENS_MIGRATION_TIME_PREFIX = '1592489784670'
15/**
16 * Base path to contract stub partials
17 */
18const CONTRACTS_PARTIALS_BASE = './contract/partials';
19/**
20 * Base path to config stub partials
21 */
22const CONFIG_PARTIALS_BASE = './config/partials';
23/**
24 * Prompt choices for the provider selection
25 */
26const PROVIDER_PROMPT_CHOICES = [
27 {
28 name: 'lucid',
29 message: 'Lucid',
30 hint: ' (Uses Data Models)',
31 },
32 {
33 name: 'database',
34 message: 'Database',
35 hint: ' (Uses Database QueryBuilder)',
36 },
37];
38/**
39 * Prompt choices for the guard selection
40 */
41const GUARD_PROMPT_CHOICES = [
42 {
43 name: 'web',
44 message: 'Web',
45 hint: ' (Uses sessions for managing auth state)',
46 },
47 {
48 name: 'api',
49 message: 'API tokens',
50 hint: ' (Uses database backed opaque tokens)',
51 },
52 {
53 name: 'basic',
54 message: 'Basic Auth',
55 hint: ' (Uses HTTP Basic auth for authenticating requests)',
56 },
57];
58/**
59 * Prompt choices for the tokens provider selection
60 */
61const TOKENS_PROVIDER_PROMPT_CHOICES = [
62 {
63 name: 'database',
64 message: 'Database',
65 hint: ' (Uses SQL table for storing API tokens)',
66 },
67 {
68 name: 'redis',
69 message: 'Redis',
70 hint: ' (Uses Redis for storing API tokens)',
71 },
72];
73/**
74 * Returns absolute path to the stub relative from the templates
75 * directory
76 */
77function getStub(...relativePaths) {
78 return (0, path_1.join)(__dirname, 'templates', ...relativePaths);
79}
80/**
81 * Creates the model file
82 */
83function makeModel(projectRoot, app, sink, state) {
84 const modelsDirectory = app.resolveNamespaceDirectory('models') || 'app/Models';
85 const modelPath = (0, path_1.join)(modelsDirectory, `${state.modelName}.ts`);
86 const template = new sink.files.MustacheFile(projectRoot, modelPath, getStub('model.txt'));
87 if (template.exists()) {
88 sink.logger.action('create').skipped(`${modelPath} file already exists`);
89 return;
90 }
91 template.apply(state).commit();
92 sink.logger.action('create').succeeded(modelPath);
93}
94/**
95 * Create the migration file
96 */
97function makeUsersMigration(projectRoot, app, sink, state) {
98 const migrationsDirectory = app.directoriesMap.get('migrations') || 'database';
99 const migrationPath = (0, path_1.join)(migrationsDirectory, `${Date.now()}_${state.usersTableName}.ts`);
100 const template = new sink.files.MustacheFile(projectRoot, migrationPath, getStub('migrations/auth.txt'));
101 if (template.exists()) {
102 sink.logger.action('create').skipped(`${migrationPath} file already exists`);
103 return;
104 }
105 template.apply(state).commit();
106 sink.logger.action('create').succeeded(migrationPath);
107}
108/**
109 * Create the migration file
110 */
111function makeTokensMigration(projectRoot, app, sink, state) {
112 const migrationsDirectory = app.directoriesMap.get('migrations') || 'database';
113 const migrationPath = (0, path_1.join)(migrationsDirectory, `${Date.now()}_${state.tokensTableName}.ts`);
114 const template = new sink.files.MustacheFile(projectRoot, migrationPath, getStub('migrations/api_tokens.txt'));
115 if (template.exists()) {
116 sink.logger.action('create').skipped(`${migrationPath} file already exists`);
117 return;
118 }
119 template.apply(state).commit();
120 sink.logger.action('create').succeeded(migrationPath);
121}
122/**
123 * Create the middleware(s)
124 */
125function makeMiddleware(projectRoot, app, sink, state) {
126 const middlewareDirectory = app.resolveNamespaceDirectory('middleware') || 'app/Middleware';
127 /**
128 * Auth middleware
129 */
130 const authPath = (0, path_1.join)(middlewareDirectory, 'Auth.ts');
131 const authTemplate = new sink.files.MustacheFile(projectRoot, authPath, getStub('middleware/Auth.txt'));
132 if (authTemplate.exists()) {
133 sink.logger.action('create').skipped(`${authPath} file already exists`);
134 }
135 else {
136 authTemplate.apply(state).commit();
137 sink.logger.action('create').succeeded(authPath);
138 }
139 /**
140 * Silent auth middleware
141 */
142 const silentAuthPath = (0, path_1.join)(middlewareDirectory, 'SilentAuth.ts');
143 const silentAuthTemplate = new sink.files.MustacheFile(projectRoot, silentAuthPath, getStub('middleware/SilentAuth.txt'));
144 if (silentAuthTemplate.exists()) {
145 sink.logger.action('create').skipped(`${silentAuthPath} file already exists`);
146 }
147 else {
148 silentAuthTemplate.apply(state).commit();
149 sink.logger.action('create').succeeded(silentAuthPath);
150 }
151}
152/**
153 * Creates the contract file
154 */
155function makeContract(projectRoot, app, sink, state) {
156 const contractsDirectory = app.directoriesMap.get('contracts') || 'contracts';
157 const contractPath = (0, path_1.join)(contractsDirectory, 'auth.ts');
158 const template = new sink.files.MustacheFile(projectRoot, contractPath, getStub('contract/auth.txt'));
159 template.overwrite = true;
160 const partials = {
161 provider: getStub(CONTRACTS_PARTIALS_BASE, `user-provider-${state.provider}.txt`),
162 };
163 state.guards.forEach((guard) => {
164 partials[`${guard}_guard`] = getStub(CONTRACTS_PARTIALS_BASE, `${guard}-guard.txt`);
165 });
166 template.apply(state).partials(partials).commit();
167 sink.logger.action('create').succeeded(contractPath);
168}
169/**
170 * Makes the auth config file
171 */
172function makeConfig(projectRoot, app, sink, state) {
173 const configDirectory = app.directoriesMap.get('config') || 'config';
174 const configPath = (0, path_1.join)(configDirectory, 'auth.ts');
175 const template = new sink.files.MustacheFile(projectRoot, configPath, getStub('config/auth.txt'));
176 template.overwrite = true;
177 const partials = {
178 provider: getStub(CONFIG_PARTIALS_BASE, `user-provider-${state.provider}.txt`),
179 token_provider: getStub(CONFIG_PARTIALS_BASE, `tokens-provider-${state.tokensProvider}.txt`),
180 };
181 state.guards.forEach((guard) => {
182 partials[`${guard}_guard`] = getStub(CONFIG_PARTIALS_BASE, `${guard}-guard.txt`);
183 });
184 template.apply(state).partials(partials).commit();
185 sink.logger.action('create').succeeded(configPath);
186}
187/**
188 * Prompts user to select the provider
189 */
190async function getProvider(sink) {
191 return sink.getPrompt().choice('Select provider for finding users', PROVIDER_PROMPT_CHOICES, {
192 validate(choice) {
193 return choice && choice.length ? true : 'Select the provider for finding users';
194 },
195 });
196}
197/**
198 * Prompts user to select the tokens provider
199 */
200async function getTokensProvider(sink) {
201 return sink
202 .getPrompt()
203 .choice('Select the provider for storing API tokens', TOKENS_PROVIDER_PROMPT_CHOICES, {
204 validate(choice) {
205 return choice && choice.length ? true : 'Select the provider for storing API tokens';
206 },
207 });
208}
209/**
210 * Prompts user to select one or more guards
211 */
212async function getGuard(sink) {
213 return sink
214 .getPrompt()
215 .multiple('Select which guard you need for authentication (select using space)', GUARD_PROMPT_CHOICES, {
216 validate(choices) {
217 return choices && choices.length
218 ? true
219 : 'Select one or more guards for authenticating users';
220 },
221 });
222}
223/**
224 * Prompts user for the model name
225 */
226async function getModelName(sink) {
227 return sink.getPrompt().ask('Enter model name to be used for authentication', {
228 validate(value) {
229 return !!value.trim().length;
230 },
231 });
232}
233/**
234 * Prompts user for the table name
235 */
236async function getTableName(sink) {
237 return sink.getPrompt().ask('Enter the database table name to look up users', {
238 validate(value) {
239 return !!value.trim().length;
240 },
241 });
242}
243/**
244 * Prompts user for the table name
245 */
246async function getMigrationConsent(sink, tableName) {
247 return sink
248 .getPrompt()
249 .confirm(`Create migration for the ${sink.logger.colors.underline(tableName)} table?`);
250}
251/**
252 * Instructions to be executed when setting up the package.
253 */
254async function instructions(projectRoot, app, sink) {
255 const state = {
256 usersTableName: '',
257 tokensTableName: 'api_tokens',
258 tokensSchemaName: 'ApiTokens',
259 usersSchemaName: '',
260 provider: 'lucid',
261 tokensProvider: 'database',
262 guards: [],
263 hasGuard: {
264 web: false,
265 api: false,
266 basic: false,
267 },
268 };
269 state.provider = await getProvider(sink);
270 state.guards = await getGuard(sink);
271 /**
272 * Need booleans for mustache templates
273 */
274 state.guards.forEach((guard) => (state.hasGuard[guard] = true));
275 /**
276 * Make model when provider is lucid otherwise prompt for the database
277 * table name
278 */
279 if (state.provider === 'lucid') {
280 const modelName = await getModelName(sink);
281 state.modelName = modelName.replace(/(\.ts|\.js)$/, '');
282 state.usersTableName = helpers_1.string.pluralize(helpers_1.string.snakeCase(state.modelName));
283 state.modelReference = helpers_1.string.camelCase(state.modelName);
284 state.modelNamespace = `${app.namespacesMap.get('models') || 'App/Models'}/${state.modelName}`;
285 }
286 else {
287 state.usersTableName = await getTableName(sink);
288 }
289 const usersMigrationConsent = await getMigrationConsent(sink, state.usersTableName);
290 let tokensMigrationConsent = false;
291 /**
292 * Only ask for the consent when using the api guard
293 */
294 if (state.hasGuard.api) {
295 state.tokensProvider = await getTokensProvider(sink);
296 if (state.tokensProvider === 'database') {
297 tokensMigrationConsent = await getMigrationConsent(sink, state.tokensTableName);
298 }
299 }
300 /**
301 * Pascal case
302 */
303 const camelCaseSchemaName = helpers_1.string.camelCase(`${state.usersTableName}_schema`);
304 state.usersSchemaName = `${camelCaseSchemaName
305 .charAt(0)
306 .toUpperCase()}${camelCaseSchemaName.slice(1)}`;
307 /**
308 * Make model when prompted for it
309 */
310 if (state.modelName) {
311 makeModel(projectRoot, app, sink, state);
312 }
313 /**
314 * Make users migration file
315 */
316 if (usersMigrationConsent) {
317 makeUsersMigration(projectRoot, app, sink, state);
318 }
319 /**
320 * Make tokens migration file
321 */
322 if (tokensMigrationConsent) {
323 makeTokensMigration(projectRoot, app, sink, state);
324 }
325 /**
326 * Make contract file
327 */
328 makeContract(projectRoot, app, sink, state);
329 /**
330 * Make config file
331 */
332 makeConfig(projectRoot, app, sink, state);
333 /**
334 * Make middleware
335 */
336 makeMiddleware(projectRoot, app, sink, state);
337}
338exports.default = instructions;