Skip to content

Commit

Permalink
Feature: seed ogcio data (#4)
Browse files Browse the repository at this point in the history
* feat(cli): added organizations seeder

* feat(cli): added rbac seeder

* feat(cli): added root seeder

* feat(cli): added ogcio seeder command

* feat(cli): added queries file

* feat(cli): use base queries

* feat(cli): added applications

* feat(cli): added resources

* feat(cli): added resources

* feat(cli): seed rbac resources

* feat(cli): updated management

* feat(cli): updated roles management

* feat(cli): rollbacked index seeder to dev

* feat(cli): moved to dedicated command

* feat(cli): prepared for local use

* feat(cli): fixed indendation in original docker compose
  • Loading branch information
SamSalvatico committed Apr 11, 2024
1 parent 79bf5bd commit d23504c
Show file tree
Hide file tree
Showing 12 changed files with 997 additions and 1 deletion.
10 changes: 10 additions & 0 deletions README.OGCIO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# LogTo per OGCIO

## Get started

If you want to run it locally, you just have to run
```
make build run
```

And, once the process ended, you're ready to open `http://localhost:3302` on your browser to navigate on your LogTo instance!
40 changes: 40 additions & 0 deletions docker-compose-local.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# This compose file is for demonstration only, do not use in prod.
version: "3.9"
services:
app:
depends_on:
postgres:
condition: service_healthy
image: local-logto:latest
entrypoint:
[
"sh",
"-c",
"npm run cli db seed -- --swe && npm run cli db ogcio && npm start"
]
ports:
- 3301:3301
- 3302:3302
environment:
- TRUST_PROXY_HEADER=1
- DB_URL=postgres://postgres:p0stgr3s@postgres:5433/logto
# Mandatory for GitPod to map host env to the container, thus GitPod can dynamically configure the public URL of Logto;
# Or, you can leverage it for local testing.
- ENDPOINT
- ADMIN_ENDPOINT
- PORT=3301
- ADMIN_PORT=3302
postgres:
image: postgres:14-alpine
user: postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: p0stgr3s
PGPORT: 5433
healthcheck:
test: [ "CMD-SHELL", "pg_isready" ]
interval: 10s
timeout: 5s
retries: 5
ports:
- 5433:5433
7 changes: 7 additions & 0 deletions makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
TAG = local-logto:latest

build:
docker build -t ${TAG} .
run:
docker-compose -f docker-compose-local.yml up --detach

9 changes: 8 additions & 1 deletion packages/cli/src/commands/database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,21 @@ import type { CommandModule } from 'yargs';

import alteration from './alteration/index.js';
import config from './config.js';
import ogcio from './ogcio/index.js';
import seed from './seed/index.js';
import system from './system.js';

const database: CommandModule = {
command: ['database', 'db'],
describe: 'Commands for Logto database',
builder: (yargs) =>
yargs.command(config).command(seed).command(alteration).command(system).demandCommand(1),
yargs
.command(config)
.command(seed)
.command(alteration)
.command(system)
.command(ogcio)
.demandCommand(1),
handler: noop,
};

Expand Down
78 changes: 78 additions & 0 deletions packages/cli/src/commands/database/ogcio/applications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/* eslint-disable eslint-comments/disable-enable-pair */

/* eslint-disable @silverhand/fp/no-mutating-methods */
/* eslint-disable @silverhand/fp/no-mutation */
import { ApplicationType } from '@logto/schemas';
import { generateStandardSecret } from '@logto/shared';
import { sql, type DatabaseTransactionConnection } from 'slonik';

import { type OgcioParams } from './index.js';
import { createItem } from './queries.js';

type ApplicationParams = Omit<OgcioParams, 'apiIndicator'>;

const createApplication = async (
transaction: DatabaseTransactionConnection,
tenantId: string,
appToSeed: SeedingApplication
) =>
createItem({
transaction,
tenantId,
toInsert: appToSeed,
toLogFieldName: 'name',
itemTypeName: 'Application',
whereClauses: [sql`name = ${appToSeed.name}`],
tableName: 'applications',
});

const setApplicationId = async (
element: SeedingApplication,
transaction: DatabaseTransactionConnection,
tenantId: string
) => {
element = await createApplication(transaction, tenantId, element);
};

const createApplications = async (
transaction: DatabaseTransactionConnection,
tenantId: string,
cliParams: ApplicationParams
): Promise<Record<string, SeedingApplication>> => {
const appsToCreate = { payments: fillPaymentsApplication(cliParams) };
const queries: Array<Promise<void>> = [];
for (const element of Object.values(appsToCreate)) {
queries.push(setApplicationId(element, transaction, tenantId));
}

await Promise.all(queries);

return appsToCreate;
};

type SeedingApplication = {
name: string;
secret: string;
description: string;
type: string;
oidc_client_metadata: string;
custom_client_metadata: string;
protected_app_metadata?: string;
is_third_party?: boolean;
};

const fillPaymentsApplication = (cliParams: ApplicationParams): SeedingApplication => ({
name: 'Life Events Payments App',
secret: generateStandardSecret(),
description: 'Payments App of Life Events',
type: ApplicationType.Traditional,
oidc_client_metadata: `{"redirectUris": ["${cliParams.appRedirectUri}"], "postLogoutRedirectUris": ["${cliParams.appLogoutRedirectUri}"]}`,
custom_client_metadata:
'{"idTokenTtl": 3600, "corsAllowedOrigins": [], "rotateRefreshToken": true, "refreshTokenTtlInDays": 14, "alwaysIssueRefreshToken": false}',
});

export const seedApplications = async (
transaction: DatabaseTransactionConnection,
tenantId: string,
cliParams: ApplicationParams
) => createApplications(transaction, tenantId, cliParams);
87 changes: 87 additions & 0 deletions packages/cli/src/commands/database/ogcio/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import type { CommandModule } from 'yargs';

import { createPoolAndDatabaseIfNeeded } from '../../../database.js';
import { consoleLog } from '../../../utils.js';

import { seedOgcio } from './ogcio.js';

export type OgcioParams = {
apiIndicator: string;
appRedirectUri: string;
appLogoutRedirectUri: string;
};

type UnknownOgcioParams = {
apiIndicator?: unknown;
appRedirectUri?: unknown;
appLogoutRedirectUri?: unknown;
};

const isValidUrl = (inputParam: string): boolean => {
try {
const _url = new URL(inputParam);
return true;
} catch {
return false;
}
};

const isValidParam = (inputParam?: unknown): inputParam is string => {
return (
inputParam !== undefined &&
typeof inputParam === 'string' &&
inputParam.length > 0 &&
isValidUrl(inputParam)
);
};

const checkParams = (inputParams: UnknownOgcioParams): Required<OgcioParams> => {
const { apiIndicator, appRedirectUri, appLogoutRedirectUri } = inputParams;
if (!isValidParam(apiIndicator)) {
throw new Error('apiIndicator must be set');
}
if (!isValidParam(appRedirectUri)) {
throw new Error('appRedirectUri must be set');
}
if (!isValidParam(appLogoutRedirectUri)) {
throw new Error('appLogoutRedirectUri must be set');
}

return { apiIndicator, appRedirectUri, appLogoutRedirectUri };
};

const ogcio: CommandModule<Partial<OgcioParams>> = {
command: 'ogcio',
describe: 'Seed OGCIO data',
builder: (yargs) =>
yargs
.option('api-indicator', {
describe: 'The root url for the seeded API resource',
type: 'string',
default: 'http://localhost:8001',
})
.option('app-redirect-uri', {
describe: 'The callback url to set for the seeded application',
type: 'string',
default: 'http://localhost:3001/callback',
})
.option('app-logout-redirect-uri', {
describe: 'The callback url to set for the seeded application',
type: 'string',
default: 'http://localhost:3001',
}),
handler: async ({ apiIndicator, appRedirectUri, appLogoutRedirectUri }) => {
const params = checkParams({ apiIndicator, appRedirectUri, appLogoutRedirectUri });
const pool = await createPoolAndDatabaseIfNeeded();
try {
await seedOgcio(pool, params);
} catch (error: unknown) {
consoleLog.error(error);
throw error;
} finally {
await pool.end();
}
},
};

export default ogcio;
40 changes: 40 additions & 0 deletions packages/cli/src/commands/database/ogcio/ogcio.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable @silverhand/fp/no-mutation */
/* eslint-disable @silverhand/fp/no-let */

import { defaultTenantId } from '@logto/schemas';
import type { CommonQueryMethods, DatabaseTransactionConnection } from 'slonik';

import { seedApplications } from './applications.js';
import { type OgcioParams } from './index.js';
import { seedOrganizationRbacData } from './organizations-rbac.js';
import { createOrganization } from './organizations.js';
import { seedResourceRbacData } from './resources-rbac.js';
import { seedResources } from './resources.js';

let inputOgcioParams: OgcioParams = {
apiIndicator: '',
appLogoutRedirectUri: '',
appRedirectUri: '',
};

const transactionMethod = async (transaction: DatabaseTransactionConnection) => {
const organizationId = await createOrganization(transaction, defaultTenantId);
const organizationRbac = await seedOrganizationRbacData(transaction, defaultTenantId);
const applications = await seedApplications(transaction, defaultTenantId, {
appRedirectUri: inputOgcioParams.appRedirectUri,
appLogoutRedirectUri: inputOgcioParams.appLogoutRedirectUri,
});
const resources = await seedResources(
transaction,
defaultTenantId,
inputOgcioParams.apiIndicator
);
const resourcesRbac = await seedResourceRbacData(transaction, defaultTenantId, resources);
};

export const seedOgcio = async (connection: CommonQueryMethods, params: OgcioParams) => {
inputOgcioParams = params;

await connection.transaction(transactionMethod);
};
Loading

0 comments on commit d23504c

Please sign in to comment.