Skip to content

Commit

Permalink
Chore: assign default organizations (#17)
Browse files Browse the repository at this point in the history
* chore(core): linked default user roles

* chore(core): fixed queries

* chore(core): added invokation during user create from dashboard

* feat(core): added ogcio user library

* chore(phrases): updated translations

* Merge branch 'dev' into chore/link-user-roles
  • Loading branch information
SamSalvatico authored May 10, 2024
1 parent a678ab4 commit 735bf18
Show file tree
Hide file tree
Showing 20 changed files with 195 additions and 1 deletion.
3 changes: 3 additions & 0 deletions docker-compose-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ services:
- ADMIN_ENDPOINT
- PORT=3301
- ADMIN_PORT=3302
- USER_DEFAULT_ORGANIZATION_NAMES=OGCIO Seeded Org
- USER_DEFAULT_ORGANIZATION_ROLE_NAMES=OGCIO Employee, OGCIO Manager
- USER_DEFAULT_ROLE_NAMES=Payments Employee,User-Profile Employee
postgres:
image: postgres:14-alpine
user: postgres
Expand Down
119 changes: 119 additions & 0 deletions packages/core/src/libraries/ogcio-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { consoleLog } from '@logto/cli/lib/utils.js';
import { type Organization, type OrganizationRole } from '@logto/schemas';
import { deduplicate } from '@silverhand/essentials';

import { EnvSet } from '#src/env-set/index.js';
import type OrganizationQueries from '#src/queries/organization/index.js';
import assertThat from '#src/utils/assert-that.js';

const getDefaultOrganizationsForUser = async (organizationQueries: OrganizationQueries) => {
const organizationNames: string[] = deduplicate(EnvSet.values.userDefaultOrganizationNames);
consoleLog.info('DEFUALT ORG NAMES', organizationNames);
if (organizationNames.length === 0) {
return [];
}
const lowerOrganizationNames = new Set(organizationNames.map((name) => name.toLowerCase()));
const allOrganizations = await organizationQueries.findAll();

const outputOrgs = allOrganizations[1].filter((fromDatabaseOrg: Organization) =>
lowerOrganizationNames.has(fromDatabaseOrg.name.toLowerCase())
);

assertThat(
outputOrgs.length === organizationNames.length,
'organization.default_organization_missing'
);

return outputOrgs;
};

const getDefaultOrganizationRolesForUser = async (organizationQueries: OrganizationQueries) => {
const roleNames: string[] = deduplicate(EnvSet.values.userDefaultOrganizationRoleNames);
consoleLog.info('DEFUALT ORG ROLE NAMES', roleNames);
if (roleNames.length === 0) {
return [];
}
const lowerRoleNames = new Set(roleNames.map((name) => name.toLowerCase()));
const limit = 200;
const offset = 0;
// eslint-disable-next-line @silverhand/fp/no-let
let outputRoleNames: OrganizationRole[] = [];
// eslint-disable-next-line @silverhand/fp/no-let
let foundCount = 1;
while (outputRoleNames.length < lowerRoleNames.size && foundCount > 0) {
// eslint-disable-next-line no-await-in-loop
const allOrganizations = await organizationQueries.roles.findAll(limit, offset);
// eslint-disable-next-line @silverhand/fp/no-mutation
foundCount = allOrganizations[0];
// eslint-disable-next-line @silverhand/fp/no-mutation
outputRoleNames = [
...outputRoleNames,
...allOrganizations[1].filter((fromDatabaseOrg: OrganizationRole) =>
lowerRoleNames.has(fromDatabaseOrg.name.toLowerCase())
),
];
}

assertThat(
outputRoleNames.length === lowerRoleNames.size,
'organization.default_organization_role_missing'
);

return outputRoleNames;
};

const getOrganizationRelationsForUser = async (organizationQueries: OrganizationQueries) => {
return {
organizations: await getDefaultOrganizationsForUser(organizationQueries),
roles: await getDefaultOrganizationRolesForUser(organizationQueries),
};
};

const insertOrganizationRelationsForUser = async (params: {
userId: string;
organizationQueries: OrganizationQueries;
organizations: Organization[];
roles: OrganizationRole[];
}) => {
if (params.organizations.length === 0) {
return;
}

const orgMappings = params.organizations.map((organization: Organization): [string, string] => [
organization.id,
params.userId,
]);

await Promise.all(
orgMappings.map(async (org) => params.organizationQueries.relations.users.insert(org))
);

if (params.roles.length > 0) {
// Org id, role id, user id
const rolesMappings: Array<[string, string, string]> = [];
for (const role of params.roles) {
for (const orgMap of orgMappings) {
// eslint-disable-next-line @silverhand/fp/no-mutating-methods
rolesMappings.push([orgMap[0], role.id, orgMap[1]]);
}
}

await Promise.all(
rolesMappings.map(async (roleMap) =>
params.organizationQueries.relations.rolesUsers.insert(roleMap)
)
);
}
};

export const manageDefaultOrganizations = async (params: {
userId: string;
organizationQueries: OrganizationQueries;
}) => {
const orgRelations = await getOrganizationRelationsForUser(params.organizationQueries);
await insertOrganizationRelationsForUser({
userId: params.userId,
...orgRelations,
organizationQueries: params.organizationQueries,
});
};
7 changes: 6 additions & 1 deletion packages/core/src/routes/admin-user/basics.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable max-lines */
import { emailRegEx, phoneRegEx, usernameRegEx } from '@logto/core-kit';
import {
UsersPasswordEncryptionMethod,
Expand All @@ -10,6 +12,7 @@ import { conditional, pick, yes } from '@silverhand/essentials';
import { boolean, literal, nativeEnum, object, string } from 'zod';

import RequestError from '#src/errors/RequestError/index.js';
import { manageDefaultOrganizations } from '#src/libraries/ogcio-user.js';
import { encryptUserPassword } from '#src/libraries/user.js';
import koaGuard from '#src/middleware/koa-guard.js';
import assertThat from '#src/utils/assert-that.js';
Expand All @@ -29,6 +32,7 @@ export default function adminUserBasicsRoutes<T extends AuthedRouter>(...args: R
hasUserWithPhone,
},
userSsoIdentities,
organizations,
} = queries;
const {
users: { checkIdentifierCollision, generateUserId, insertUser, verifyUserPassword },
Expand Down Expand Up @@ -212,6 +216,8 @@ export default function adminUserBasicsRoutes<T extends AuthedRouter>(...args: R
[]
);

await manageDefaultOrganizations({ userId: id, organizationQueries: organizations });

ctx.body = pick(user, ...userInfoSelectFields);

return next();
Expand Down Expand Up @@ -348,7 +354,6 @@ export default function adminUserBasicsRoutes<T extends AuthedRouter>(...args: R

return next();
}
// eslint-disable-next-line max-lines
);

router.delete(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { generateStandardId } from '@logto/shared';
import { conditional, conditionalArray, trySafe } from '@silverhand/essentials';

import { EnvSet } from '#src/env-set/index.js';
import { manageDefaultOrganizations } from '#src/libraries/ogcio-user.js';
import { assignInteractionResults } from '#src/libraries/session.js';
import { encryptUserPassword } from '#src/libraries/user.js';
import type { LogEntry, WithLogContext } from '#src/middleware/koa-audit-log.js';
Expand Down Expand Up @@ -174,6 +175,8 @@ async function handleSubmitRegister(
]);
}

await manageDefaultOrganizations({ userId: id, organizationQueries: organizations });

await assignInteractionResults(ctx, provider, { login: { accountId: id } });
ctx.assignInteractionHookResult({ userId: id });

Expand Down
4 changes: 4 additions & 0 deletions packages/phrases/src/locales/de/errors/organization.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const organization = {
require_membership: 'Der Benutzer muss Mitglied der Organisation sein, um fortzufahren.',
default_organization_missing:
'Einige der Standardorganisationen existieren nicht in der Datenbank, bitte stellen Sie sicher, dass Sie zuerst Organisationen erstellen',
default_organization_role_missing:
'Einige der Standard-Organisationsrollen existieren nicht in der Datenbank. Bitte stellen Sie sicher, dass Sie zuerst die Organisationsrollen erstellen',
};

export default Object.freeze(organization);
4 changes: 4 additions & 0 deletions packages/phrases/src/locales/en/errors/organization.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const organizations = {
require_membership: 'The user must be a member of the organization to proceed.',
default_organization_missing:
'Some of the default organizations does not exist in database, please ensure to create organizations first',
default_organization_role_missing:
'Some of the default organization roles does not exist in database, please ensure to create organization roles first',
};

export default Object.freeze(organizations);
4 changes: 4 additions & 0 deletions packages/phrases/src/locales/es/errors/organization.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const organization = {
require_membership: 'El usuario debe ser miembro de la organización para continuar.',
default_organization_missing:
'Algunas de las organizaciones predeterminadas no existen en la base de datos, asegúrate de crear primero las organizaciones',
default_organization_role_missing:
'Algunos de los roles de organización predeterminados no existen en la base de datos, asegúrese de crear primero los roles de organización',
};

export default Object.freeze(organization);
4 changes: 4 additions & 0 deletions packages/phrases/src/locales/fr/errors/organization.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const organization = {
require_membership: "L'utilisateur doit être membre de l'organisation pour continuer.",
default_organization_missing:
"Certaines des organisations par défaut n'existent pas dans la base de données, veuillez vous assurer de créer d'abord les organisations",
default_organization_role_missing:
"Certains des rôles d'organisation par défaut n'existent pas dans la base de données, veuillez vous assurer de créer d'abord les rôles d'organisation",
};

export default Object.freeze(organization);
4 changes: 4 additions & 0 deletions packages/phrases/src/locales/it/errors/organization.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const organization = {
require_membership: "L'utente deve essere un membro dell'organizzazione per procedere.",
default_organization_missing:
'Alcune delle organizzazioni predefinite non esistono nel database, assicurati di creare prima le organizzazioni',
default_organization_role_missing:
"Alcuni dei ruoli di organizzazione predefiniti non esistono nel database, assicurarsi di creare prima i ruoli dell'organizzazione",
};

export default Object.freeze(organization);
4 changes: 4 additions & 0 deletions packages/phrases/src/locales/ja/errors/organization.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const organization = {
require_membership: 'ユーザーは組織のメンバーである必要があります。',
default_organization_missing:
'デフォルトのいくつかの組織はデータベースに存在しません。組織を先に作成してください。',
default_organization_role_missing:
'デフォルトの組織ロールのいくつかはデータベースに存在しません。組織ロールを先に作成してください',
};

export default Object.freeze(organization);
4 changes: 4 additions & 0 deletions packages/phrases/src/locales/ko/errors/organization.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const organization = {
require_membership: '사용자는 조직의 구성원이어야 합니다.',
default_organization_missing:
'일부 기본 조직이 데이터베이스에 존재하지 않습니다. 조직을 먼저 생성해주세요',
default_organization_role_missing:
'일부 기본 조직 역할이 데이터베이스에 없습니다. 먼저 조직 역할을 만드십시오',
};

export default Object.freeze(organization);
4 changes: 4 additions & 0 deletions packages/phrases/src/locales/pl-pl/errors/organization.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const organization = {
require_membership: 'Użytkownik musi być członkiem organizacji, aby kontynuować.',
default_organization_missing:
'Niektóre domyślne organizacje nie istnieją w bazie danych, upewnij się, że najpierw utworzono organizacje',
default_organization_role_missing:
'Niektóre z domyślnych ról organizacji nie istnieją w bazie danych, upewnij się, że najpierw utworzono role organizacji',
};

export default Object.freeze(organization);
4 changes: 4 additions & 0 deletions packages/phrases/src/locales/pt-br/errors/organization.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const organization = {
require_membership: 'O usuário deve ser um membro da organização para continuar.',
default_organization_missing:
'Algumas das organizações padrão não existem no banco de dados, por favor, certifique-se de criar organizações primeiro',
default_organization_role_missing:
'Alguns dos papéis de organização padrão não existem no banco de dados, por favor, certifique-se de criar primeiro os papéis de organização',
};

export default Object.freeze(organization);
4 changes: 4 additions & 0 deletions packages/phrases/src/locales/pt-pt/errors/organization.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const organization = {
require_membership: 'O utilizador deve ser membro da organização para avançar.',
default_organization_missing:
'Algumas das organizações padrão não existem no banco de dados, por favor, certifique-se de criar organizações primeiro',
default_organization_role_missing:
'Alguns dos papéis de organização padrão não existem no banco de dados, por favor, certifique-se de criar primeiro os papéis de organização',
};

export default Object.freeze(organization);
4 changes: 4 additions & 0 deletions packages/phrases/src/locales/ru/errors/organization.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const organization = {
require_membership: 'Пользователь должен быть участником организации, чтобы продолжить.',
default_organization_missing:
'Некоторые из стандартных организаций не существуют в базе данных, пожалуйста, убедитесь, что вы создали организации сначала',
default_organization_role_missing:
'Некоторые из ролей организации по умолчанию не существуют в базе данных. Пожалуйста, убедитесь, что сначала созданы роли организации',
};

export default Object.freeze(organization);
4 changes: 4 additions & 0 deletions packages/phrases/src/locales/tr-tr/errors/organization.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const organization = {
require_membership: 'Kullanıcının devam etmek için organizasyonun bir üyesi olması gerekir.',
default_organization_missing:
'Niektóre domyślne organizacje nie istnieją w bazie danych, upewnij się, że najpierw utworzono organizacje',
default_organization_role_missing:
'Varsayılan organizasyon rollerinden bazıları veritabanında mevcut değil, lütfen önce organizasyon rollerini oluşturduğunuzdan emin olun',
};

export default Object.freeze(organization);
2 changes: 2 additions & 0 deletions packages/phrases/src/locales/zh-cn/errors/organization.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const organization = {
require_membership: '用户必须是组织的成员才能继续。',
default_organization_missing: '一些默认组织在数据库中不存在,请确保首先创建组织',
default_organization_role_missing: '某些默认组织角色不存在于数据库中,请确保先创建组织角色',
};

export default Object.freeze(organization);
2 changes: 2 additions & 0 deletions packages/phrases/src/locales/zh-hk/errors/organization.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const organization = {
require_membership: '用戶必須是組織的成員才能繼續。',
default_organization_missing: '一些默认组织在数据库中不存在,请确保首先创建组织',
default_organization_role_missing: '某些默认组织角色不存在于数据库中,请确保先创建组织角色',
};

export default Object.freeze(organization);
2 changes: 2 additions & 0 deletions packages/phrases/src/locales/zh-tw/errors/organization.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const organization = {
require_membership: '使用者必須是該組織的成員才能繼續。',
default_organization_missing: '一些默认组织在数据库中不存在,请确保首先创建组织',
default_organization_role_missing: '某些默认组织角色不存在于数据库中,请确保先创建组织角色',
};

export default Object.freeze(organization);
10 changes: 10 additions & 0 deletions packages/shared/src/node/env/GlobalValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,16 @@ export default class GlobalValues {
return this.urlSet.endpoint;
}

/** OGCIO Definitions */
public readonly userDefaultOrganizationNames = getEnvAsStringArray(
'USER_DEFAULT_ORGANIZATION_NAMES'
);

public readonly userDefaultOrganizationRoleNames = getEnvAsStringArray(
'USER_DEFAULT_ORGANIZATION_ROLE_NAMES'
);
/** END OGCIO Definitions */

constructor() {
if (this.isPathBasedMultiTenancy) {
console.warn(
Expand Down

0 comments on commit 735bf18

Please sign in to comment.