Skip to content

Commit

Permalink
feat: auth with email and password (#13)
Browse files Browse the repository at this point in the history
* feat: allow authentication with email/password

* docs: add config example directus-sync.config.js
  • Loading branch information
EdouardDem authored Nov 30, 2023
1 parent 04b1b83 commit be605e9
Show file tree
Hide file tree
Showing 21 changed files with 178 additions and 84 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ Moreover, `directus-sync` organizes backups into multiple files, significantly i
easier to track and review changes. This thoughtful separation facilitates a smoother version control process, allowing
for targeted updates and clearer oversight of your Directus configurations.

# Requirements

- Node.js 18 or higher
- `directus-extension-sync` installed on your Directus instance. See the [installation instructions](#dependency-directus-extension-sync).

# Usage

The CLI is available using the `npx` command.
Expand Down Expand Up @@ -86,6 +91,13 @@ These options can be used with any command to configure the operation of `direct

- `-t, --directus-token <directusToken>`
Provide the Directus access token. Alternatively, set the `DIRECTUS_TOKEN` environment variable.
If provided, the `directus-email` and `directus-password` options are ignored.

- `-e, --directus-email <directusEmail> `
Provide the Directus email. Alternatively, set the `DIRECTUS_ADMIN_EMAIL` environment variable.

- `-p, --directus-password <directusPassword>`
Provide the Directus password. Alternatively, set the `DIRECTUS_ADMIN_PASSWORD` environment variable.

- `--no-split`
Indicates whether the schema snapshot should be split into multiple files. By default, snapshots are split.
Expand Down Expand Up @@ -125,6 +137,8 @@ module.exports = {
debug: true,
directusUrl: 'https://directus.example.com',
directusToken: 'my-directus-token',
directusEmail: 'admin@example.com', // ignored if directusToken is provided
directusPassword: 'my-directus-password', // ignored if directusToken is provided
split: true,
dumpPath: './directus-config',
collectionsPath: 'collections',
Expand Down
1 change: 1 addition & 0 deletions packages/cli/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
directus-config/
README.md
directus-sync.config.js
3 changes: 1 addition & 2 deletions packages/cli/.npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,4 @@ test/
example.env
jest.config.js
tsconfig.json
test/files/config-loader/basic/directus-sync.config.js
directus-sync.config.*.js
directus-sync.config.js
10 changes: 10 additions & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ const directusTokenOption = new Option(
'-t, --directus-token <directusToken>',
'Directus access token',
).env('DIRECTUS_TOKEN');
const directusEmailOption = new Option(
'-e, --directus-email <directusEmail>',
'Directus user email',
).env('DIRECTUS_ADMIN_EMAIL');
const directusPasswordOption = new Option(
'-p, --directus-password <directusPassword>',
'Directus user password',
).env('DIRECTUS_ADMIN_PASSWORD');
const configPathOption = new Option(
'-c, --config-path <configPath>',
`the path to the config file. Required for extended options (default "${DefaultConfig.configPath}")`,
Expand Down Expand Up @@ -57,6 +65,8 @@ program
.addOption(debugOption)
.addOption(directusUrlOption)
.addOption(directusTokenOption)
.addOption(directusEmailOption)
.addOption(directusPasswordOption)
.addOption(configPathOption);

program
Expand Down
8 changes: 4 additions & 4 deletions packages/cli/src/lib/services/collections/base/data-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export abstract class DataClient<DirectusType extends DirectusBaseType> {
async query<T extends object = DirectusType>(
query: Query<DirectusType>,
): Promise<T[]> {
const directus = this.migrationClient.get();
const directus = await this.migrationClient.get();
const response = await directus.request<T | T[]>(
await this.getQueryCommand(query),
);
Expand All @@ -39,7 +39,7 @@ export abstract class DataClient<DirectusType extends DirectusBaseType> {
* Remove the id and the syncId from the item before inserting it.
*/
async create(item: WithoutIdAndSyncId<DirectusType>): Promise<DirectusType> {
const directus = this.migrationClient.get();
const directus = await this.migrationClient.get();
return await directus.request(await this.getInsertCommand(item));
}

Expand All @@ -51,7 +51,7 @@ export abstract class DataClient<DirectusType extends DirectusBaseType> {
itemId: DirectusId,
diffItem: Partial<WithoutIdAndSyncId<DirectusType>>,
): Promise<DirectusType> {
const directus = this.migrationClient.get();
const directus = await this.migrationClient.get();
return await directus.request(
await this.getUpdateCommand(itemId, diffItem),
);
Expand All @@ -62,7 +62,7 @@ export abstract class DataClient<DirectusType extends DirectusBaseType> {
* The id is the local id.
*/
async delete(itemId: DirectusId): Promise<DirectusType> {
const directus = this.migrationClient.get();
const directus = await this.migrationClient.get();
return await directus.request(await this.getDeleteCommand(itemId));
}

Expand Down
32 changes: 19 additions & 13 deletions packages/cli/src/lib/services/collections/base/id-mapper-client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import createHttpError from 'http-errors';
import { ConfigService } from '../../config';
import { MigrationClient } from '../../migration-client';
import { Cacheable } from 'typescript-cacheable';

export interface IdMap {
id: number;
Expand All @@ -12,10 +13,6 @@ export interface IdMap {
export abstract class IdMapperClient {
protected readonly extensionUri = '/directus-extension-sync';

protected readonly url: string;

protected readonly token: string;

/**
* Cache for id maps
*/
Expand All @@ -28,13 +25,9 @@ export abstract class IdMapperClient {
};

constructor(
protected readonly config: ConfigService,
protected readonly migrationClient: MigrationClient,
protected readonly table: string,
) {
const { url, token } = config.getDirectusConfig();
this.url = url;
this.token = token;
}
) {}

async getBySyncId(syncId: string): Promise<IdMap | undefined> {
// Try to get from cache
Expand Down Expand Up @@ -126,11 +119,12 @@ export abstract class IdMapperClient {
payload: unknown = undefined,
options: RequestInit = {},
): Promise<T> {
const response = await fetch(`${this.url}${this.extensionUri}${uri}`, {
const { url, token } = await this.getUrlAndToken();
const response = await fetch(`${url}${this.extensionUri}${uri}`, {
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${this.token}`,
Authorization: `Bearer ${token}`,
},
method,
body: payload ? JSON.stringify(payload) : null,
Expand Down Expand Up @@ -161,6 +155,18 @@ export abstract class IdMapperClient {
}
}

@Cacheable()
protected async getUrlAndToken() {
const directus = await this.migrationClient.get();
//Remove trailing slash
const url = directus.url.toString().replace(/\/$/, '');
const token = await directus.getToken();
if (!token) {
throw new Error('Cannot get token from Directus');
}
return { url, token };
}

protected addToCache(idMap: IdMap) {
this.cache.bySyncId.set(idMap.sync_id, idMap);
this.cache.byLocalId.set(idMap.local_id, idMap);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { IdMapperClient } from '../base';
import { Service } from 'typedi';
import { DASHBOARDS_COLLECTION } from './constants';
import { ConfigService } from '../../config';
import { MigrationClient } from '../../migration-client';

@Service()
export class DashboardsIdMapperClient extends IdMapperClient {
constructor(config: ConfigService) {
super(config, DASHBOARDS_COLLECTION);
constructor(migrationClient: MigrationClient) {
super(migrationClient, DASHBOARDS_COLLECTION);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { IdMapperClient } from '../base';
import { Service } from 'typedi';
import { FLOWS_COLLECTION } from './constants';
import { ConfigService } from '../../config';
import { MigrationClient } from '../../migration-client';

@Service()
export class FlowsIdMapperClient extends IdMapperClient {
constructor(config: ConfigService) {
super(config, FLOWS_COLLECTION);
constructor(migrationClient: MigrationClient) {
super(migrationClient, FLOWS_COLLECTION);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { IdMapperClient } from '../base';
import { Service } from 'typedi';
import { OPERATIONS_COLLECTION } from './constants';
import { ConfigService } from '../../config';
import { MigrationClient } from '../../migration-client';

@Service()
export class OperationsIdMapperClient extends IdMapperClient {
constructor(config: ConfigService) {
super(config, OPERATIONS_COLLECTION);
constructor(migrationClient: MigrationClient) {
super(migrationClient, OPERATIONS_COLLECTION);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { IdMapperClient } from '../base';
import { Service } from 'typedi';
import { PANELS_COLLECTION } from './constants';
import { ConfigService } from '../../config';
import { MigrationClient } from '../../migration-client';

@Service()
export class PanelsIdMapperClient extends IdMapperClient {
constructor(config: ConfigService) {
super(config, PANELS_COLLECTION);
constructor(migrationClient: MigrationClient) {
super(migrationClient, PANELS_COLLECTION);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { IdMapperClient } from '../base';
import { Service } from 'typedi';
import { PERMISSIONS_COLLECTION } from './constants';
import { ConfigService } from '../../config';
import { MigrationClient } from '../../migration-client';

@Service()
export class PermissionsIdMapperClient extends IdMapperClient {
constructor(config: ConfigService) {
super(config, PERMISSIONS_COLLECTION);
constructor(migrationClient: MigrationClient) {
super(migrationClient, PERMISSIONS_COLLECTION);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { IdMapperClient } from '../base';
import { Service } from 'typedi';
import { ROLES_COLLECTION } from './constants';
import { ConfigService } from '../../config';
import { MigrationClient } from '../../migration-client';

@Service()
export class RolesIdMapperClient extends IdMapperClient {
constructor(config: ConfigService) {
super(config, ROLES_COLLECTION);
constructor(migrationClient: MigrationClient) {
super(migrationClient, ROLES_COLLECTION);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { IdMapperClient } from '../base';
import { Service } from 'typedi';
import { SETTINGS_COLLECTION } from './constants';
import { ConfigService } from '../../config';
import { MigrationClient } from '../../migration-client';

@Service()
export class SettingsIdMapperClient extends IdMapperClient {
constructor(config: ConfigService) {
super(config, SETTINGS_COLLECTION);
constructor(migrationClient: MigrationClient) {
super(migrationClient, SETTINGS_COLLECTION);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { IdMapperClient } from '../base';
import { Service } from 'typedi';
import { WEBHOOKS_COLLECTION } from './constants';
import { ConfigService } from '../../config';
import { MigrationClient } from '../../migration-client';

@Service()
export class WebhooksIdMapperClient extends IdMapperClient {
constructor(config: ConfigService) {
super(config, WEBHOOKS_COLLECTION);
constructor(migrationClient: MigrationClient) {
super(migrationClient, WEBHOOKS_COLLECTION);
}
}
Loading

0 comments on commit be605e9

Please sign in to comment.