@memberjunction/server 0.9.73 → 0.9.74

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/config.ts CHANGED
@@ -1,76 +1,76 @@
1
- import env from 'env-var';
2
- import fs from 'fs';
3
- import path from 'path';
4
- import { z } from 'zod';
5
-
6
- export const nodeEnv = env.get('NODE_ENV').asString();
7
-
8
- export const dbHost = env.get('DB_HOST').required().asString();
9
- export const dbPort = env.get('DB_PORT').default('1433').asPortNumber();
10
- export const dbUsername = env.get('DB_USERNAME').required().asString();
11
- export const dbPassword = env.get('DB_PASSWORD').required().asString();
12
- export const dbDatabase = env.get('DB_DATABASE').required().asString();
13
- export const dbInstanceName = env.get('DB_INSTANCE_NAME').asString();
14
- export const dbTrustServerCertificate = env.get('DB_TRUST_SERVER_CERTIFICATE').asBool();
15
-
16
- export const graphqlPort = env.get('PORT').default('4000').asPortNumber();
17
- export const graphqlRootPath = env.get('ROOT_PATH').default('/').asString();
18
-
19
- export const webClientID = env.get('WEB_CLIENT_ID').asString();
20
- export const tenantID = env.get('TENANT_ID').asString();
21
-
22
- export const enableIntrospection = env.get('ENABLE_INTROSPECTION').default('false').asBool();
23
- export const websiteRunFromPackage = env.get('WEBSITE_RUN_FROM_PACKAGE').asIntPositive();
24
- export const userEmailMap = env.get('USER_EMAIL_MAP').default('{}').asJsonObject() as Record<string, string>;
25
-
26
- export const auth0Domain = env.get('AUTH0_DOMAIN').asString();
27
- export const auth0WebClientID = env.get('AUTH0_CLIENT_ID').asString();
28
- export const auth0ClientSecret = env.get('AUTH0_CLIENT_SECRET').asString();
29
-
30
- export const mj_core_schema = env.get('MJ_CORE_SCHEMA').asString();
31
-
32
- export const configFile = env.get('CONFIG_FILE').asString();
33
-
34
- const userHandlingInfoSchema = z.object({
35
- autoCreateNewUsers: z.boolean().optional().default(false),
36
- newUserAuthorizedDomains: z.array(z.string()).optional().default([]),
37
- newUserRoles: z.array(z.string()).optional().default([]),
38
- updateCacheWhenNotFound: z.boolean().optional().default(false),
39
- updateCacheWhenNotFoundDelay: z.number().optional().default(30000),
40
- contextUserForNewUserCreation: z.string().optional().default(''),
41
- });
42
-
43
- const databaseSettingsInfoSchema = z.object({
44
- connectionTimeout: z.number(),
45
- requestTimeout: z.number(),
46
- metadataCacheRefreshInterval: z.number(),
47
- });
48
-
49
- const viewingSystemInfoSchema = z.object({
50
- enableSmartFilters: z.boolean().optional(),
51
- });
52
-
53
-
54
- const configInfoSchema = z.object({
55
- userHandling: userHandlingInfoSchema,
56
- databaseSettings: databaseSettingsInfoSchema,
57
- viewingSystem: viewingSystemInfoSchema.optional(),
58
- });
59
-
60
- export type UserHandlingInfo = z.infer<typeof userHandlingInfoSchema>;
61
- export type DatabaseSettingsInfo = z.infer<typeof databaseSettingsInfoSchema>;
62
- export type ViewingSystemSettingsInfo = z.infer<typeof viewingSystemInfoSchema>;
63
- export type ConfigInfo = z.infer<typeof configInfoSchema>;
64
-
65
- export const configInfo: ConfigInfo = loadConfig();
66
-
67
- export function loadConfig() {
68
- const configPath = configFile ?? path.resolve('config.json');
69
-
70
- if (!fs.existsSync(configPath)) {
71
- throw new Error(`Config file ${configPath} does not exist.`);
72
- }
73
-
74
- const configData = fs.readFileSync(configPath, 'utf-8');
75
- return configInfoSchema.parse(JSON.parse(configData));
76
- }
1
+ import env from 'env-var';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { z } from 'zod';
5
+
6
+ export const nodeEnv = env.get('NODE_ENV').asString();
7
+
8
+ export const dbHost = env.get('DB_HOST').required().asString();
9
+ export const dbPort = env.get('DB_PORT').default('1433').asPortNumber();
10
+ export const dbUsername = env.get('DB_USERNAME').required().asString();
11
+ export const dbPassword = env.get('DB_PASSWORD').required().asString();
12
+ export const dbDatabase = env.get('DB_DATABASE').required().asString();
13
+ export const dbInstanceName = env.get('DB_INSTANCE_NAME').asString();
14
+ export const dbTrustServerCertificate = env.get('DB_TRUST_SERVER_CERTIFICATE').asBool();
15
+
16
+ export const graphqlPort = env.get('PORT').default('4000').asPortNumber();
17
+ export const graphqlRootPath = env.get('ROOT_PATH').default('/').asString();
18
+
19
+ export const webClientID = env.get('WEB_CLIENT_ID').asString();
20
+ export const tenantID = env.get('TENANT_ID').asString();
21
+
22
+ export const enableIntrospection = env.get('ENABLE_INTROSPECTION').default('false').asBool();
23
+ export const websiteRunFromPackage = env.get('WEBSITE_RUN_FROM_PACKAGE').asIntPositive();
24
+ export const userEmailMap = env.get('USER_EMAIL_MAP').default('{}').asJsonObject() as Record<string, string>;
25
+
26
+ export const auth0Domain = env.get('AUTH0_DOMAIN').asString();
27
+ export const auth0WebClientID = env.get('AUTH0_CLIENT_ID').asString();
28
+ export const auth0ClientSecret = env.get('AUTH0_CLIENT_SECRET').asString();
29
+
30
+ export const mj_core_schema = env.get('MJ_CORE_SCHEMA').asString();
31
+
32
+ export const configFile = env.get('CONFIG_FILE').asString();
33
+
34
+ const userHandlingInfoSchema = z.object({
35
+ autoCreateNewUsers: z.boolean().optional().default(false),
36
+ newUserAuthorizedDomains: z.array(z.string()).optional().default([]),
37
+ newUserRoles: z.array(z.string()).optional().default([]),
38
+ updateCacheWhenNotFound: z.boolean().optional().default(false),
39
+ updateCacheWhenNotFoundDelay: z.number().optional().default(30000),
40
+ contextUserForNewUserCreation: z.string().optional().default(''),
41
+ });
42
+
43
+ const databaseSettingsInfoSchema = z.object({
44
+ connectionTimeout: z.number(),
45
+ requestTimeout: z.number(),
46
+ metadataCacheRefreshInterval: z.number(),
47
+ });
48
+
49
+ const viewingSystemInfoSchema = z.object({
50
+ enableSmartFilters: z.boolean().optional(),
51
+ });
52
+
53
+
54
+ const configInfoSchema = z.object({
55
+ userHandling: userHandlingInfoSchema,
56
+ databaseSettings: databaseSettingsInfoSchema,
57
+ viewingSystem: viewingSystemInfoSchema.optional(),
58
+ });
59
+
60
+ export type UserHandlingInfo = z.infer<typeof userHandlingInfoSchema>;
61
+ export type DatabaseSettingsInfo = z.infer<typeof databaseSettingsInfoSchema>;
62
+ export type ViewingSystemSettingsInfo = z.infer<typeof viewingSystemInfoSchema>;
63
+ export type ConfigInfo = z.infer<typeof configInfoSchema>;
64
+
65
+ export const configInfo: ConfigInfo = loadConfig();
66
+
67
+ export function loadConfig() {
68
+ const configPath = configFile ?? path.resolve('config.json');
69
+
70
+ if (!fs.existsSync(configPath)) {
71
+ throw new Error(`Config file ${configPath} does not exist.`);
72
+ }
73
+
74
+ const configData = fs.readFileSync(configPath, 'utf-8');
75
+ return configInfoSchema.parse(JSON.parse(configData));
76
+ }
package/src/context.ts CHANGED
@@ -1,106 +1,106 @@
1
- import { IncomingMessage } from 'http';
2
- import * as url from 'url';
3
- import { JwtPayload, VerifyOptions, decode, verify } from 'jsonwebtoken';
4
- import 'reflect-metadata';
5
- import { Subject, firstValueFrom } from 'rxjs';
6
- import { AuthenticationError, AuthorizationError } from 'type-graphql';
7
- import { DataSource } from 'typeorm';
8
- import { getSigningKeys, validationOptions, verifyUserRecord } from './auth';
9
- import { authCache } from './cache';
10
- import { userEmailMap } from './config';
11
- import { UserPayload } from './types';
12
-
13
- const verifyAsync = async (
14
- issuer: string,
15
- options: VerifyOptions,
16
- token: string
17
- ): Promise<JwtPayload> =>
18
- new Promise((resolve, reject) => {
19
- verify(token, getSigningKeys(issuer), options, (err, jwt) => {
20
- if (jwt && typeof jwt !== 'string' && !err) {
21
- const payload = jwt.payload ?? jwt;
22
-
23
- console.log(
24
- `Valid token: ${payload.name} (${
25
- payload.email ? payload.email : payload.preferred_username
26
- })`
27
- ); // temporary fix to check preferred_username if email is not present
28
- resolve(payload);
29
- } else {
30
- console.warn('Invalid token');
31
- reject(err);
32
- }
33
- });
34
- });
35
-
36
- export const getUserPayload = async (
37
- bearerToken: string,
38
- sessionId = 'default',
39
- dataSource: DataSource,
40
- requestDomain?: string
41
- ): Promise<UserPayload> => {
42
- try {
43
- const token = bearerToken.replace('Bearer ', '');
44
-
45
- if (!token) {
46
- console.warn('No token to validate');
47
- throw new AuthenticationError('Missing token');
48
- }
49
-
50
- const payload = decode(token);
51
- if (!payload || typeof payload === 'string') {
52
- throw new AuthenticationError('Invalid token payload');
53
- }
54
-
55
- if (!authCache.has(token)) {
56
- const issuer = payload.iss;
57
- if (!issuer) {
58
- console.warn('No issuer claim on token');
59
- throw new AuthenticationError('Missing issuer claim on token');
60
- }
61
-
62
- await verifyAsync(issuer, validationOptions[issuer], token);
63
- authCache.set(token, true);
64
- }
65
-
66
- const email = payload?.email
67
- ? userEmailMap[payload?.email] ?? payload?.email
68
- : payload?.preferred_username; // temporary fix to check preferred_username if email is not present
69
- const fullName = payload?.name;
70
- const firstName = payload?.given_name || fullName?.split(' ')[0];
71
- const lastName = payload?.family_name || fullName?.split(' ')[1] || fullName?.split(' ')[0];
72
- const userRecord = await verifyUserRecord(email, firstName, lastName, requestDomain, dataSource);
73
-
74
- if (!userRecord) {
75
- console.error(`User ${email} not found`);
76
- throw new AuthorizationError();
77
- }
78
- else if (!userRecord.IsActive) {
79
- console.error(`User ${email} found but inactive`);
80
- throw new AuthorizationError();
81
- }
82
-
83
- return { userRecord, email, sessionId };
84
- } catch (e) {
85
- console.error(e);
86
- return {} as UserPayload;
87
- }
88
- };
89
-
90
- export const contextFunction =
91
- ({ setupComplete$, dataSource }: { setupComplete$: Subject<unknown>; dataSource: DataSource }) =>
92
- async ({ req }: { req: IncomingMessage }) => {
93
- await firstValueFrom(setupComplete$); // wait for setup to complete before processing the request
94
-
95
- const sessionIdRaw = req.headers['x-session-id'];
96
- const requestDomain = url.parse(req.headers.origin || '')
97
- const sessionId = sessionIdRaw ? sessionIdRaw.toString() : '';
98
- const bearerToken = req.headers.authorization ?? '';
99
-
100
- const userPayload = await getUserPayload(bearerToken, sessionId, dataSource, requestDomain?.hostname ? requestDomain.hostname : undefined);
101
-
102
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
103
- console.log((req as any).body?.operationName);
104
-
105
- return { dataSource, userPayload };
106
- };
1
+ import { IncomingMessage } from 'http';
2
+ import * as url from 'url';
3
+ import { JwtPayload, VerifyOptions, decode, verify } from 'jsonwebtoken';
4
+ import 'reflect-metadata';
5
+ import { Subject, firstValueFrom } from 'rxjs';
6
+ import { AuthenticationError, AuthorizationError } from 'type-graphql';
7
+ import { DataSource } from 'typeorm';
8
+ import { getSigningKeys, validationOptions, verifyUserRecord } from './auth';
9
+ import { authCache } from './cache';
10
+ import { userEmailMap } from './config';
11
+ import { UserPayload } from './types';
12
+
13
+ const verifyAsync = async (
14
+ issuer: string,
15
+ options: VerifyOptions,
16
+ token: string
17
+ ): Promise<JwtPayload> =>
18
+ new Promise((resolve, reject) => {
19
+ verify(token, getSigningKeys(issuer), options, (err, jwt) => {
20
+ if (jwt && typeof jwt !== 'string' && !err) {
21
+ const payload = jwt.payload ?? jwt;
22
+
23
+ console.log(
24
+ `Valid token: ${payload.name} (${
25
+ payload.email ? payload.email : payload.preferred_username
26
+ })`
27
+ ); // temporary fix to check preferred_username if email is not present
28
+ resolve(payload);
29
+ } else {
30
+ console.warn('Invalid token');
31
+ reject(err);
32
+ }
33
+ });
34
+ });
35
+
36
+ export const getUserPayload = async (
37
+ bearerToken: string,
38
+ sessionId = 'default',
39
+ dataSource: DataSource,
40
+ requestDomain?: string
41
+ ): Promise<UserPayload> => {
42
+ try {
43
+ const token = bearerToken.replace('Bearer ', '');
44
+
45
+ if (!token) {
46
+ console.warn('No token to validate');
47
+ throw new AuthenticationError('Missing token');
48
+ }
49
+
50
+ const payload = decode(token);
51
+ if (!payload || typeof payload === 'string') {
52
+ throw new AuthenticationError('Invalid token payload');
53
+ }
54
+
55
+ if (!authCache.has(token)) {
56
+ const issuer = payload.iss;
57
+ if (!issuer) {
58
+ console.warn('No issuer claim on token');
59
+ throw new AuthenticationError('Missing issuer claim on token');
60
+ }
61
+
62
+ await verifyAsync(issuer, validationOptions[issuer], token);
63
+ authCache.set(token, true);
64
+ }
65
+
66
+ const email = payload?.email
67
+ ? userEmailMap[payload?.email] ?? payload?.email
68
+ : payload?.preferred_username; // temporary fix to check preferred_username if email is not present
69
+ const fullName = payload?.name;
70
+ const firstName = payload?.given_name || fullName?.split(' ')[0];
71
+ const lastName = payload?.family_name || fullName?.split(' ')[1] || fullName?.split(' ')[0];
72
+ const userRecord = await verifyUserRecord(email, firstName, lastName, requestDomain, dataSource);
73
+
74
+ if (!userRecord) {
75
+ console.error(`User ${email} not found`);
76
+ throw new AuthorizationError();
77
+ }
78
+ else if (!userRecord.IsActive) {
79
+ console.error(`User ${email} found but inactive`);
80
+ throw new AuthorizationError();
81
+ }
82
+
83
+ return { userRecord, email, sessionId };
84
+ } catch (e) {
85
+ console.error(e);
86
+ return {} as UserPayload;
87
+ }
88
+ };
89
+
90
+ export const contextFunction =
91
+ ({ setupComplete$, dataSource }: { setupComplete$: Subject<unknown>; dataSource: DataSource }) =>
92
+ async ({ req }: { req: IncomingMessage }) => {
93
+ await firstValueFrom(setupComplete$); // wait for setup to complete before processing the request
94
+
95
+ const sessionIdRaw = req.headers['x-session-id'];
96
+ const requestDomain = url.parse(req.headers.origin || '')
97
+ const sessionId = sessionIdRaw ? sessionIdRaw.toString() : '';
98
+ const bearerToken = req.headers.authorization ?? '';
99
+
100
+ const userPayload = await getUserPayload(bearerToken, sessionId, dataSource, requestDomain?.hostname ? requestDomain.hostname : undefined);
101
+
102
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
103
+ console.log((req as any).body?.operationName);
104
+
105
+ return { dataSource, userPayload };
106
+ };
@@ -1,42 +1,42 @@
1
- import { FieldMapper, MapperKind, getDirective, mapSchema } from '@graphql-tools/utils';
2
- import { GraphQLFieldResolver, defaultFieldResolver } from 'graphql';
3
- import { AuthorizationError, Directive } from 'type-graphql';
4
- import { AppContext, DirectiveBuilder } from '../types';
5
-
6
- const DIRECTIVE_NAME = 'Public';
7
-
8
- export function Public(): PropertyDecorator & MethodDecorator & ClassDecorator;
9
- export function Public(): PropertyDecorator | MethodDecorator | ClassDecorator {
10
- return (targetOrPrototype, propertyKey, descriptor) =>
11
- Directive(`@${DIRECTIVE_NAME}`)(targetOrPrototype, propertyKey, descriptor);
12
- }
13
-
14
- export const publicDirective: DirectiveBuilder = {
15
- typeDefs: `directive @${DIRECTIVE_NAME} on FIELD_DEFINITION`,
16
- transformer: (schema) => {
17
- const fieldMapper: FieldMapper = (fieldConfig) => {
18
- const directive = getDirective(schema, fieldConfig, DIRECTIVE_NAME)?.[0];
19
- if (directive) {
20
- return fieldConfig;
21
- } else {
22
- // `@Public` directive not present, so will require auth
23
- const { resolve = defaultFieldResolver } = fieldConfig;
24
- const directiveResolver: GraphQLFieldResolver<unknown, AppContext> = async (
25
- source,
26
- args,
27
- context,
28
- info
29
- ) => {
30
- // eslint-disable-next-line
31
- if (!context?.userPayload?.userRecord?.IsActive) {
32
- throw new AuthorizationError();
33
- }
34
- return await resolve(source, args, context, info);
35
- };
36
-
37
- return { ...fieldConfig, resolve: directiveResolver };
38
- }
39
- };
40
- return mapSchema(schema, { [MapperKind.OBJECT_FIELD]: fieldMapper });
41
- },
42
- };
1
+ import { FieldMapper, MapperKind, getDirective, mapSchema } from '@graphql-tools/utils';
2
+ import { GraphQLFieldResolver, defaultFieldResolver } from 'graphql';
3
+ import { AuthorizationError, Directive } from 'type-graphql';
4
+ import { AppContext, DirectiveBuilder } from '../types';
5
+
6
+ const DIRECTIVE_NAME = 'Public';
7
+
8
+ export function Public(): PropertyDecorator & MethodDecorator & ClassDecorator;
9
+ export function Public(): PropertyDecorator | MethodDecorator | ClassDecorator {
10
+ return (targetOrPrototype, propertyKey, descriptor) =>
11
+ Directive(`@${DIRECTIVE_NAME}`)(targetOrPrototype, propertyKey, descriptor);
12
+ }
13
+
14
+ export const publicDirective: DirectiveBuilder = {
15
+ typeDefs: `directive @${DIRECTIVE_NAME} on FIELD_DEFINITION`,
16
+ transformer: (schema) => {
17
+ const fieldMapper: FieldMapper = (fieldConfig) => {
18
+ const directive = getDirective(schema, fieldConfig, DIRECTIVE_NAME)?.[0];
19
+ if (directive) {
20
+ return fieldConfig;
21
+ } else {
22
+ // `@Public` directive not present, so will require auth
23
+ const { resolve = defaultFieldResolver } = fieldConfig;
24
+ const directiveResolver: GraphQLFieldResolver<unknown, AppContext> = async (
25
+ source,
26
+ args,
27
+ context,
28
+ info
29
+ ) => {
30
+ // eslint-disable-next-line
31
+ if (!context?.userPayload?.userRecord?.IsActive) {
32
+ throw new AuthorizationError();
33
+ }
34
+ return await resolve(source, args, context, info);
35
+ };
36
+
37
+ return { ...fieldConfig, resolve: directiveResolver };
38
+ }
39
+ };
40
+ return mapSchema(schema, { [MapperKind.OBJECT_FIELD]: fieldMapper });
41
+ },
42
+ };
@@ -1 +1 @@
1
- export * from './Public';
1
+ export * from './Public';