Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: release #107

Merged
merged 10 commits into from
Aug 26, 2024
1,566 changes: 1,243 additions & 323 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"cz-conventional-changelog": "^3.3.0",
"eslint-plugin-file-extension-in-import-ts": "^2.1.0",
"eslint-plugin-unicorn": "^53.0.0",
"lerna": "^8.1.3",
"lerna": "=8.1.2",
"nx": "^19.2.0",
"tar": "^7.2.0"
},
Expand Down
86 changes: 79 additions & 7 deletions packages/acs-client/src/acs/decorators.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as uuid from 'uuid';
import { Logger } from 'winston';
import { Logger } from '@restorecommerce/logger';
import { Provider as ServiceConfig } from 'nconf';
import {
Client,
Expand All @@ -14,7 +14,8 @@ import {
Response_Decision
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/access_control';
import {
ResourceList
ResourceList,
ResourceListResponse
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/resource_base';
import {
initAuthZ,
Expand All @@ -35,13 +36,18 @@ import {
} from './resolver';
import { cfg } from '../config';
import { _ } from '../utils';
import {
Filter_Operation,
Filter_ValueType
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/filter';

/* eslint-disable prefer-arrow-functions/prefer-arrow-functions */
export type DatabaseProvider = 'arangoDB' | 'postgres';
export type ACSClientContextFactory<T extends ResourceList> = (self: any, request: T, ...args: any) => Promise<ACSClientContext>;
export type ResourceFactory<T extends ResourceList> = (self: any, request: T, ...args: any) => Promise<Resource[]>;
export type DatabaseSelector<T extends ResourceList> = (self: any, request: T, ...args: any) => Promise<DatabaseProvider>;
export type MetaDataInjector<T extends ResourceList> = (self: any, request: T, ...args: any) => Promise<T>;
export type SubjectResolver<T extends ResourceList> = (self: any, request: T, ...args: any) => Promise<T>;

export interface AccessControlledService {
readonly __userService: Client<UserServiceDefinition>;
Expand All @@ -58,9 +64,9 @@ export const DefaultACSClientContextFactory = async <T extends ResourceList>(
resources: [],
});

export function DefaultResourceFactory<T extends ResourceList>(
export const DefaultResourceFactory = <T extends ResourceList>(
...resourceNames: string[]
): ResourceFactory<T> {
): ResourceFactory<T> => {
return async (
self: any,
request: T,
Expand All @@ -71,21 +77,68 @@ export function DefaultResourceFactory<T extends ResourceList>(
id: request.items?.map((item: any) => item.id)
})
);
}
};

export const DefaultSubjectResolver = async <T extends ResourceList>(
self: any,
request: T,
...args: any
): Promise<T> => {
const subject = request?.subject;
if (subject?.id) {
delete subject.id;
}
if (subject?.token) {
const user = await self.__userService.findByToken({ token: subject.token });
if (user?.payload?.id) {
subject.id = user.payload.id;
}
}
return request;
};

export const DefaultMetaDataInjector = async <T extends ResourceList>(
self: any,
request: T,
...args: any
): Promise<T> => {
const urns = cfg.get('authorization:urns');
const ids = [...new Set(
request.items?.map(
(item) => item.id
).filter(
id => id
)
)
];
const meta_map = ids.length && await self.read({
filters: [{
filters: [{
field: 'id',
operation: Filter_Operation.in,
value: JSON.stringify(ids),
type: Filter_ValueType.ARRAY,
filters: [],
}]
}],
limit: ids.length,
subject: request.subject
}).then(
(response: ResourceListResponse) => new Map(response.items?.filter(
item => item.payload
).map(
item => [item.payload.id, item.payload.meta]
))
);

request.items?.forEach((item) => {
if (!item.id?.length) {
item.id = uuid.v4().replace(/-/g, '');
}

if (!item.meta?.owners?.length) {
item.meta = {
...meta_map.get(item.id),
...item.meta,
owners: [
request.subject?.scope ? {
Expand Down Expand Up @@ -171,7 +224,9 @@ export function access_controlled_function<T extends ResourceList>(kwargs: {
: kwargs.database;

const subject = context?.subject;
subject.id = null;
if (subject) {
subject.id = null;
}
if (subject?.token) {
const user = await that.__userService.findByToken({ token: subject.token });
if (user?.payload?.id) {
Expand Down Expand Up @@ -225,8 +280,25 @@ export function access_controlled_function<T extends ResourceList>(kwargs: {
};
}

export function resolves_subject<T extends ResourceList>(
subjectResolver: SubjectResolver<T> = DefaultSubjectResolver<T>,
) {
return function (
target: any,
propertyName: string,
descriptor: TypedPropertyDescriptor<any>,
) {
const method = descriptor.value!;
descriptor.value = async function () {
const args = [...arguments].slice(1);
const request = await subjectResolver(this, arguments[0], ...args);
return await method.apply(this, [request, ...args]);
};
};
};

export function injects_meta_data<T extends ResourceList>(
metaDataInjector: MetaDataInjector<T> = DefaultMetaDataInjector < T >,
metaDataInjector: MetaDataInjector<T> = DefaultMetaDataInjector<T>,
) {
return function (
target: any,
Expand Down
45 changes: 30 additions & 15 deletions packages/facade/src/gql/protos/resolvers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { authSubjectType, type ProtoMetadata, type ServiceClient, type ServiceConfig, type SubSpaceServiceConfig } from './types.js';
import {
authSubjectType,
type ProtoMetadata,
type ServiceClient,
type ServiceConfig,
type SubSpaceServiceConfig
} from './types.js';
import flat from 'array.prototype.flat';
import { getTyping } from './registry.js';
import {
Expand All @@ -8,7 +14,6 @@ import {
import {
camelCase,
capitalize,
convertEnumToInt,
decodeBufferFields,
getKeys,
snakeToCamel,
Expand Down Expand Up @@ -123,7 +128,7 @@ export const getGQLResolverFunctions =
(obj as any)[methodName] = async (args: any, context: ServiceClient<CTX, keyof CTX, T>) => {
// remap namespace and serviceKey if given
key = cfg?.namespace ?? key;
serviceKey = cfg?.serviceKey ?? serviceKey;
serviceKey = cfg?.serviceKeyMap?.[serviceKey] ?? cfg?.serviceKey ?? serviceKey;
const client = context[key].client;
const service = client[serviceKey];
try {
Expand Down Expand Up @@ -306,7 +311,15 @@ export const registerResolverFunction = <T extends Record<string, any>, CTX exte
}
}
if (service) {
const key = namespace?.toLocaleLowerCase() + '.' + subspace?.toLocaleLowerCase() + '.' + name?.toLocaleLowerCase();
const key = [
namespace,
subspace,
name
].filter(
s => s
).join(
'.'
).toLocaleLowerCase();
const value = service.method.find((m) => m.name === name);
if (key && value?.inputType) {
inputMethodType.set(key, value.inputType);
Expand Down Expand Up @@ -370,7 +383,15 @@ export const generateResolver = (...namespaces: string[]) => {
return resolvers;
};

export const generateSubServiceResolvers = <T, M extends Record<string, any>, CTX extends ServiceClient<CTX, keyof CTX, M>>(subServices: ProtoMetadata[], config: SubSpaceServiceConfig, namespace: string): T => {
export const generateSubServiceResolvers = <
T,
M extends Record<string, any>,
CTX extends ServiceClient<CTX, keyof CTX, M>
>(
subServices: ProtoMetadata[],
config: SubSpaceServiceConfig,
namespace: string,
): T => {
subServices.forEach((meta) => {
meta.fileDescriptor.service.forEach(service => {
if (service.name) {
Expand Down Expand Up @@ -515,15 +536,9 @@ export const generateSubServiceResolvers = <T, M extends Record<string, any>, CT
return undefined;
}

// rename master_data and ostorage name space to actual service names in proto files
if (resolver.targetService == 'master_data') {
resolver.targetService = 'resource';
} else if (resolver.targetService == 'ostorage') {
resolver.targetSubService = 'ostorage';
}
const client = ctx[resolver.targetService as string].client;
const service = client[resolver.targetSubService as string];

resolver.targetService = config?.namespace ?? resolver.targetService
const client = ctx[resolver.targetService].client;
const service = client[resolver.targetSubService];
const idList: string[] = Array.isArray(parent[fieldJsonName]) ? parent[fieldJsonName] : [parent[fieldJsonName]];

// TODO Support custom input messages
Expand All @@ -550,7 +565,7 @@ export const generateSubServiceResolvers = <T, M extends Record<string, any>, CT
req.subject!.token = await fetchUnauthenticatedUserToken(ctx, (ctx as any).request!.req.headers['origin']);
}

const methodFunc = service[camelCase(resolver.targetMethod as string)] || service[resolver.targetMethod as string];
const methodFunc = service[camelCase(resolver.targetMethod)] || service[resolver.targetMethod];
const result = await methodFunc(req);

if (result && result.items && result.items.length) {
Expand Down
6 changes: 0 additions & 6 deletions packages/facade/src/gql/protos/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,6 @@ export const decodeBufferFields = (items: any[], bufferFields: string[]): any =>
}
};

interface EnumMetaData {
name: string;
number: number;
options?: any;
}

/**
* recursively find the id and updates the object with given value, this function
* also takes care to handle if there is an array at any position in the path
Expand Down
22 changes: 16 additions & 6 deletions packages/facade/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { createLogger } from '@restorecommerce/logger';
import { type Logger } from 'winston';
import { createLogger, type Logger } from '@restorecommerce/logger';
import { type Server, ServerResponse } from 'node:http';

import Koa from 'koa';
Expand Down Expand Up @@ -30,7 +29,6 @@ import { setUseSubscriptions } from './gql/protos/utils.js';
import bodyParser from 'koa-bodyparser';
import cors from '@koa/cors';
import graphqlUploadKoa from 'graphql-upload/graphqlUploadKoa.mjs';
import helmet from 'koa-helmet';
import Router from 'koa-router';

export * from './modules/index.js';
Expand All @@ -48,6 +46,7 @@ interface RestoreCommerceFacadeImplConfig {
kafka?: KafkaProviderConfig['kafka'];
fileUploadOptions?: FileUploadOptionsConfig['fileUploadOptions'];
jsonLimit?: string;
extraServices?: { name: string, url: string }[];
}

interface FacadeApolloServiceMap {
Expand All @@ -74,11 +73,12 @@ export class RestoreCommerceFacade<TModules extends FacadeModuleBase[] = []> imp
readonly kafkaConfig?: KafkaProviderConfig['kafka'];
readonly fileUploadOptionsConfig?: FileUploadOptionsConfig['fileUploadOptions'];
readonly jsonLimit?: string;
readonly extraServices?: { name: string, url: string }[];

private startFns: Array<(() => Promise<void>)> = [];
private stopFns: Array<(() => Promise<void>)> = [];

constructor({ koa, logger, port, hostname, env, kafka, fileUploadOptions, jsonLimit }: RestoreCommerceFacadeImplConfig) {
constructor({ koa, logger, port, hostname, env, kafka, fileUploadOptions, jsonLimit, extraServices }: RestoreCommerceFacadeImplConfig) {
this.logger = logger;
this.port = port ?? 5000;
this.hostname = hostname ?? '127.0.0.1';
Expand All @@ -87,6 +87,7 @@ export class RestoreCommerceFacade<TModules extends FacadeModuleBase[] = []> imp
this.kafkaConfig = kafka;
this.fileUploadOptionsConfig = fileUploadOptions;
this.jsonLimit = jsonLimit ? jsonLimit : '50mb';
this.extraServices = extraServices;

setUseSubscriptions(!!kafka);
}
Expand Down Expand Up @@ -207,13 +208,20 @@ export class RestoreCommerceFacade<TModules extends FacadeModuleBase[] = []> imp
}

private async mountApolloServer() {
const serviceList = Object.keys(this.apolloServices).map(key => {
let serviceList = Object.keys(this.apolloServices).map(key => {
return {
name: key,
url: this.apolloServices[key].url ?? `local`,
};
});

if (this.extraServices) {
serviceList = [
...serviceList,
...this.extraServices,
];
}

const gateway = new ApolloGateway({
logger: this.logger,
// serviceList,
Expand Down Expand Up @@ -348,6 +356,7 @@ export interface FacadeConfig {
kafka?: KafkaProviderConfig['kafka'];
fileUploadOptions?: FileUploadOptionsConfig['fileUploadOptions'];
jsonLimit?: string;
extraServices?: { name: string, url: string }[];
}

export const createFacade = (config: FacadeConfig): Facade => {
Expand Down Expand Up @@ -380,6 +389,7 @@ export const createFacade = (config: FacadeConfig): Facade => {
env: config.env,
kafka: config.kafka,
fileUploadOptions: config.fileUploadOptions,
jsonLimit: config.jsonLimit
jsonLimit: config.jsonLimit,
extraServices: config.extraServices
}).useModule(facadeStatusModule);
};
2 changes: 1 addition & 1 deletion packages/facade/src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type Koa from 'koa';
import { type Logger } from 'winston';
import { type Logger } from '@restorecommerce/logger';
import { type Server } from 'node:http';
import { type AddressInfo } from 'node:net';

Expand Down
4 changes: 2 additions & 2 deletions packages/facade/src/modules/identity/grpc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class IdentitySrvGrpcClient extends RestoreCommerceGrpcClient {
readonly role: RoleServiceClient;
readonly authentication_log: AuthenticationLogServiceClient;
readonly token: TokenServiceClient;
readonly oauth: OAuthServiceClient;
readonly o_auth: OAuthServiceClient;

constructor(address: string, cfg: GrpcClientConfig) {
super(address, cfg);
Expand All @@ -36,7 +36,7 @@ export class IdentitySrvGrpcClient extends RestoreCommerceGrpcClient {
this.role = this.createClient(cfg, RoleServiceDefinition, this.channel);
this.authentication_log = this.createClient(cfg, AuthenticationLogServiceDefinition, this.channel);
this.token = this.createClient(cfg, TokenServiceDefinition, this.channel);
this.oauth = this.createClient(cfg, OAuthServiceDefinition, this.channel);
this.o_auth = this.createClient(cfg, OAuthServiceDefinition, this.channel);
}

}
2 changes: 1 addition & 1 deletion packages/facade/src/modules/identity/oauth/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export const createOAuth = (): KoaRouter<{}, IdentityContext> => {

router.get('/oauth2/:service', async (ctx, next) => {
const ids = ctx.identitySrvClient as IdentitySrvGrpcClient;
const user = await ids.oauth.exchangeCode({
const user = await ids.o_auth.exchangeCode({
service: ctx.params.service,
code: ctx.request.query['code'] as string,
state: ctx.request.query['state'] as string
Expand Down
Loading
Loading