0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

feat: post affiliate data to cloud (#4321)

* feat: post affiliate data to cloud

Read data from cookie and post to cloud when needed.

* chore: add alteration script

* refactor: fix alteration
This commit is contained in:
Gao Sun 2023-08-16 22:34:54 +08:00 committed by GitHub
parent 1c18111344
commit 2901f43e64
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 120 additions and 8 deletions

View file

@ -28,6 +28,7 @@
"@aws-sdk/client-s3": "^3.315.0",
"@azure/storage-blob": "^12.13.0",
"@koa/cors": "^4.0.0",
"@logto/affiliate": "^0.1.0",
"@logto/app-insights": "workspace:^1.3.1",
"@logto/cli": "workspace:^1.7.0",
"@logto/connector-kit": "workspace:^1.1.1",

View file

@ -1,3 +1,4 @@
import { defaults, parseAffiliateData } from '@logto/affiliate';
import { Component, CoreEvent, getEventName } from '@logto/app-insights/custom-event';
import { appInsights } from '@logto/app-insights/node';
import type { User, Profile, CreateUser } from '@logto/schemas';
@ -11,14 +12,17 @@ import {
adminConsoleApplicationId,
} from '@logto/schemas';
import { type OmitAutoSetFields } from '@logto/shared';
import { conditional, conditionalArray } from '@silverhand/essentials';
import { conditional, conditionalArray, trySafe } from '@silverhand/essentials';
import { type IRouterContext } from 'koa-router';
import { EnvSet } from '#src/env-set/index.js';
import { type CloudConnectionLibrary } from '#src/libraries/cloud-connection.js';
import type { ConnectorLibrary } from '#src/libraries/connector.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';
import type TenantContext from '#src/tenants/TenantContext.js';
import { consoleLog } from '#src/utils/console.js';
import { getTenantId } from '#src/utils/tenant.js';
import type { WithInteractionDetailsContext } from '../middleware/koa-interaction-details.js';
@ -164,10 +168,45 @@ const parseUserProfile = async (
};
};
const getInitialUserRoles = (
isInAdminTenant: boolean,
isCreatingFirstAdminUser: boolean,
isCloud: boolean
) =>
conditionalArray<string>(
isInAdminTenant && AdminTenantRole.User,
isCreatingFirstAdminUser && getManagementApiAdminName(defaultTenantId),
isCreatingFirstAdminUser && isCloud && getManagementApiAdminName(adminTenantId)
);
/** Post affiliate data to the cloud service. */
const postAffiliateLogs = async (
ctx: IRouterContext,
cloudConnection: CloudConnectionLibrary,
userId: string,
tenantId: string
) => {
if (!EnvSet.values.isCloud || tenantId !== adminTenantId) {
return;
}
const affiliateData = trySafe(() =>
parseAffiliateData(JSON.parse(decodeURIComponent(ctx.cookies.get(defaults.cookieName) ?? '')))
);
if (affiliateData) {
const client = await cloudConnection.getClient();
await client.post('/api/affiliate-logs', {
body: { userId, ...affiliateData },
});
consoleLog.info('Affiliate logs posted', userId);
}
};
export default async function submitInteraction(
interaction: VerifiedInteractionResult,
ctx: WithLogContext & WithInteractionDetailsContext & WithInteractionHooksContext,
{ provider, libraries, connectors, queries }: TenantContext,
{ provider, libraries, connectors, queries, cloudConnection, id: tenantId }: TenantContext,
log?: LogEntry
) {
const { hasActiveUsers, findUserById, updateUserById } = queries.users;
@ -196,11 +235,7 @@ export default async function submitInteraction(
id,
...userProfile,
},
conditionalArray<string>(
isInAdminTenant && AdminTenantRole.User,
isCreatingFirstAdminUser && getManagementApiAdminName(defaultTenantId),
isCreatingFirstAdminUser && isCloud && getManagementApiAdminName(adminTenantId)
)
getInitialUserRoles(isInAdminTenant, isCreatingFirstAdminUser, isCloud)
);
// In OSS, we need to limit sign-in experience to "sign-in only" once
@ -216,6 +251,10 @@ export default async function submitInteraction(
log?.append({ userId: id });
appInsights.client?.trackEvent({ name: getEventName(Component.Core, CoreEvent.Register) });
void trySafe(postAffiliateLogs(ctx, cloudConnection, id, tenantId), (error) => {
consoleLog.warn('Failed to post affiliate logs', error);
void appInsights.trackException(error);
});
return;
}

View file

@ -0,0 +1,57 @@
import { generateStandardId } from '@logto/shared/universal';
import { sql } from 'slonik';
import type { AlterationScript } from '../lib/types/alteration.js';
const adminTenantId = 'admin';
const alteration: AlterationScript = {
up: async (pool) => {
// Get `resourceId` of the admin tenant's resource whose indicator is `https://cloud.logto.io/api`.
const { id: resourceId } = await pool.one<{ id: string }>(sql`
select id from resources
where tenant_id = ${adminTenantId}
and indicator = 'https://cloud.logto.io/api'
`);
const { id: roleId } = await pool.one<{ id: string }>(sql`
select id from roles
where tenant_id = ${adminTenantId}
and name = 'admin:admin'
`);
const createAffiliateId = generateStandardId();
const manageAffiliateId = generateStandardId();
await pool.query(sql`
insert into scopes (tenant_id, id, name, description, resource_id)
values (
${adminTenantId},
${createAffiliateId},
'create:affiliate',
'Allow creating new affiliates and logs.',
${resourceId}
), (
${adminTenantId},
${manageAffiliateId},
'manage:affiliate',
'Allow managing affiliates, including create, update, and delete.',
${resourceId}
);
`);
await pool.query(sql`
insert into roles_scopes (tenant_id, id, role_id, scope_id) values
(${adminTenantId}, ${generateStandardId()}, ${roleId}, ${createAffiliateId}),
(${adminTenantId}, ${generateStandardId()}, ${roleId}, ${manageAffiliateId});
`);
},
down: async (pool) => {
await pool.query(sql`
delete from scopes
where tenant_id = ${adminTenantId} and name = any(array['create:affiliate', 'manage:affiliate']);
`);
},
};
export default alteration;

View file

@ -66,6 +66,11 @@ export const createCloudApi = (): Readonly<[UpdateAdminData, ...CreateScope[]]>
CloudScope.SendSms,
'Allow sending SMS. This scope is only available to M2M application.'
),
buildScope(CloudScope.CreateAffiliate, 'Allow creating new affiliates and logs.'),
buildScope(
CloudScope.ManageAffiliate,
'Allow managing affiliates, including create, update, and delete.'
),
]);
};

View file

@ -3124,6 +3124,9 @@ importers:
'@koa/cors':
specifier: ^4.0.0
version: 4.0.0
'@logto/affiliate':
specifier: ^0.1.0
version: 0.1.0
'@logto/app-insights':
specifier: workspace:^1.3.1
version: link:../app-insights
@ -7290,6 +7293,14 @@ packages:
dev: true
optional: true
/@logto/affiliate@0.1.0:
resolution: {integrity: sha512-yDWSZMI2Qo/xoYU92tnwSP/gnSvq8+CLK5DqD/4brO42QJa7xjt7eA+HSyuMmSUrKffY2nP3riU81gs+nR8DkA==}
engines: {node: ^18.12.0}
dependencies:
'@silverhand/essentials': 2.8.2
tiny-cookie: 2.4.1
dev: false
/@logto/browser@2.1.0:
resolution: {integrity: sha512-4XsXlCC0uZHcfazV09/4YKo4koqvSzQlkPUAToTp/WHpb6h2XDOJh5/hi55LXL4zp0PCcgpErKRxFCtgXCc6WQ==}
dependencies:
@ -19705,7 +19716,6 @@ packages:
/tiny-cookie@2.4.1:
resolution: {integrity: sha512-h8ueaMyvUd/9ZfRqCfa1t+0tXqfVFhdK8WpLHz8VXMqsiaj3Sqg64AOCH/xevLQGZk0ZV+/75ouITdkvp3taVA==}
dev: true
/tiny-glob@0.2.9:
resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==}