Skip to content

Commit

Permalink
Merge pull request #854 from cam-inc/fix/auth-oidc-audit-support
Browse files Browse the repository at this point in the history
fix(example): vulnerability Assessment Support
  • Loading branch information
takoring authored Feb 13, 2025
2 parents 7483ff5 + ea00971 commit bec1cce
Show file tree
Hide file tree
Showing 29 changed files with 590 additions and 74 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/publish-package-viorn-lib.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Publish NPM Package - Viron/lib
on:
push:
branches:
- develop
- fix/auth-oidc-audit-support
paths:
- "packages/nodejs/**"

Expand Down
4 changes: 3 additions & 1 deletion example/nodejs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"main": "dist/server.js",
"dependencies": {
"@aws-sdk/client-s3": "^3.451.0",
"@viron/lib": "2.4.0-alpha.0",
"@types/sanitize-html": "^2.13.0",
"@viron/lib": "2.4.0-alpha.10",
"accepts": "^1.3.7",
"compression": "^1.7.4",
"cookie-parser": "^1.4.5",
Expand All @@ -23,6 +24,7 @@
"openid-client": "^4.7.4",
"pino": "^7.6.4",
"pino-http": "^6.6.0",
"sanitize-html": "^2.14.0",
"sequelize": "^6.5.0",
"uuid": "^8.3.2"
},
Expand Down
4 changes: 4 additions & 0 deletions example/nodejs/src/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { domainsAuth, domainsOas } from '@viron/lib';
import { middlewareI18n } from './middlewares/i18n';
import { middlewareNotFound } from './middlewares/notfound';
import { middlewareCors } from './middlewares/cors';
import { csrf as middlewareCsrf } from './middlewares/csrf';
import { middlewareAccessLog } from './middlewares/accesslog';
import { middlewareAuditLog } from './middlewares/auditlog';
import { middlewareCacheControl } from './middlewares/cachecontrol';
Expand Down Expand Up @@ -69,6 +70,9 @@ export const createApplication = async (): Promise<Express> => {
app.use(json());
app.use(urlencoded({ extended: true }));
app.use(cookieParser());
if (ctx.config.csrf) {
app.use(middlewareCsrf(ctx.config.csrf));
}
app.use(middlewareAccessLog());
app.use(middlewareI18n());
app.use(middlewareCacheControl());
Expand Down
13 changes: 13 additions & 0 deletions example/nodejs/src/config/development.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ export const get = (): Config => {
'https://snapshot.viron.work',
],
},
csrf: {
host: 'demo.viron.work',
allowOrigins: [
'https://localhost:8000',
'https://viron.work',
'https://snapshot.viron.work',
],
ignorePaths: [
'/ping',
'/oidc/authorization',
'/oauth2/google/authorization',
],
},
auth: {
jwt: {
secret: process.env.JWT_SECRET ?? '',
Expand Down
7 changes: 7 additions & 0 deletions example/nodejs/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ export interface CorsConfig {
allowOrigins: string[];
}

export interface ConfigCsrf {
host: string;
allowOrigins: string[];
ignorePaths: string[];
}

export interface OasConfig {
infoExtentions: domainsOas.VironInfoObjectExtentions;
}
Expand All @@ -45,6 +51,7 @@ export interface Config {
vironLib: MongoConfig | MysqlConfig;
};
cors: CorsConfig;
csrf?: ConfigCsrf;
auth: {
jwt: domainsAuth.JwtConfig;
googleOAuth2: domainsAuth.GoogleOAuthConfig;
Expand Down
13 changes: 13 additions & 0 deletions example/nodejs/src/config/local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,19 @@ export const get = (mode: Mode): Config => {
'https://snapshot.viron.work',
],
},
csrf: {
host: 'example.viron.work:3000',
allowOrigins: [
'https://localhost:8000',
'https://viron.work',
'https://snapshot.viron.work',
],
ignorePaths: [
'/ping',
'/oidc/authorization',
'/oauth2/google/authorization',
],
},
auth: {
jwt: {
secret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
Expand Down
9 changes: 9 additions & 0 deletions example/nodejs/src/config/production.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ export const get = (): Config => {
cors: {
allowOrigins: ['https://viron.plus', 'https://snapshot.viron.plus'],
},
csrf: {
host: 'demo.viron.plus',
allowOrigins: ['https://viron.plus', 'https://snapshot.viron.plus'],
ignorePaths: [
'/ping',
'/oidc/authorization',
'/oauth2/google/authorization',
],
},
auth: {
jwt: {
secret: process.env.JWT_SECRET ?? '',
Expand Down
2 changes: 2 additions & 0 deletions example/nodejs/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ export const AUTHENTICATION_RESULT_TYPE = {
} as const;

export const LIMIT_MEDIA_FILE_SIZE = 2000000;

export const CSRF_IGNORE_HTTP_METHODS = ['HEAD', 'OPTIONS'];
5 changes: 4 additions & 1 deletion example/nodejs/src/controllers/adminroles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import { RouteContext } from '../application';
export const listVironAdminRoles = async (
context: RouteContext
): Promise<void> => {
const { size, page } = context.params.query;
const result = await domainsAdminRole.listByOas(
context.req._context.apiDefinition
context.req._context.apiDefinition,
size,
page
);

context.res.json(result);
Expand Down
4 changes: 2 additions & 2 deletions example/nodejs/src/controllers/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ export const downloadResources = async (
context: RouteContext
): Promise<void> => {
const { resourceName } = context.params.path;
const { format = 'json' } = context.params.query;
const result = await exportResources(resourceName, format);
const { format = 'json', size, page, sort } = context.params.query;
const result = await exportResources(resourceName, format, size, page, sort);
context.res.header(HTTP_HEADER.CONTENT_TYPE, contentType(format) || '');
context.res.header(
HTTP_HEADER.CONTENT_DISPOSITION,
Expand Down
16 changes: 10 additions & 6 deletions example/nodejs/src/domains/resources.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
import { TABLE_SORT_DELIMITER, TABLE_SORT_ORDER } from '@viron/lib';
import { getRepository, RepositoryNames } from '../repositories';

// データエクスポート
export const exportResources = async (
resourceName: string,
format: 'json' | 'csv'
format: 'json' | 'csv',
size?: number,
page?: number,
sort = [`createdAt${TABLE_SORT_DELIMITER}${TABLE_SORT_ORDER.DESC}`]
// eslint-disable-next-line @typescript-eslint/ban-types
): Promise<object | string> => {
const repository = getRepository(resourceName as RepositoryNames);
if (!repository) {
return format === 'json' ? {} : '';
}

const result = await repository.find();
const result = await repository.findWithPager({}, size, page, sort);
switch (format) {
case 'csv': {
if (!result.length) {
if (!result.list.length) {
return '';
}
const headers = Object.keys(result[0]);
const headers = Object.keys(result.list[0]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return result.reduce((ret: string, doc: any) => {
return result.list.reduce((ret: string, doc: any) => {
const line: unknown[] = [];
Object.entries(doc).forEach(([k, v]) => {
line[headers.indexOf(k)] = v;
Expand All @@ -28,6 +32,6 @@ export const exportResources = async (
}, headers.join(','));
}
default:
return result;
return result.list;
}
};
71 changes: 71 additions & 0 deletions example/nodejs/src/middlewares/csrf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { NextFunction, Request, Response, RequestHandler } from 'express';
import { ConfigCsrf } from '../config';
import { logger } from '../context';
import { CSRF_IGNORE_HTTP_METHODS } from '../constants';
import { forbidden } from '@viron/lib';

export const csrf = (options: ConfigCsrf): RequestHandler => {
logger.info('Use middlwares#csrf. %O', options);
// allowOriginsに *.examle.com のようなワイルドカードを指定可能にするために正規表現を生成
const regExps = options.allowOrigins.map((allowOrigin) => {
return {
allowOrigin: allowOrigin,
re: new RegExp(`^${allowOrigin.replace(/\*/g, '.*')}$`),
};
});
const match = (
obj: { allowOrigin: string; re: RegExp },
origin: string
): boolean => {
return obj.re.test(origin);
};

const middleware: RequestHandler = (
req: Request,
_res: Response,
next: NextFunction
) => {
const host = req.get('host');
const origin = req.get('origin');

// ignorePathsに含まれていれば無視
if (!options.ignorePaths || options.ignorePaths.includes(req.path)) {
return next();
}

logger.debug('csrf options: %o', options);
logger.debug('csrf host: %s', host);
logger.debug('csrf method: %s', req.method);
logger.debug('csrf headers: %o', req.headers);
logger.debug('csrf origin: %s', origin);

// CSRF対象外のHTTPメソッドの場合は無視
if (CSRF_IGNORE_HTTP_METHODS.includes(req.method)) {
return next();
}

// originがない場合は403
if (!origin) {
logger.error('csrf illegal origin. origin: %s', origin);
return next(forbidden());
}

// hostが異なる場合は403
if (options.host !== host) {
logger.error('csrf illegal host . host: %s', host);
return next(forbidden());
}

// allowOriginsに含まれていればOK
const isAllow = regExps.find((obj) => {
return match(obj, origin);
});
if (isAllow) {
return next();
}

logger.error('csrf illegal origin. origin: %s', origin);
next(forbidden());
};
return middleware;
};
54 changes: 45 additions & 9 deletions example/nodejs/src/middlewares/errorhandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,31 @@ import { ErrorRequestHandler } from 'express';
import accepts from 'accepts';
import { HTTP_HEADER } from '@viron/lib';
import { logger } from '../context';
import sanitizeHtml from 'sanitize-html';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const stringify = (val: any): string => {
if (val instanceof Error) {
return String(val.stack ?? val);
}
return JSON.stringify(val);
// 特殊文字をUnicodeエスケープする
const escapeToUnicode = (input: string): string => {
return input.replace(/[<>"'&]/g, (char) => {
switch (char) {
case '<':
return '\\u003C';
case '>':
return '\\u003E';
case '&':
return '\\u0026';
case '"':
return '\\u0022';
case "'":
return '\\u0027';
default:
return char;
}
});
};

// サニタイズ
const sanitizeErrorContent = (content: string): string => {
return escapeToUnicode(sanitizeHtml(content));
};

export const middlewareErrorHandler = (): ErrorRequestHandler => {
Expand Down Expand Up @@ -37,13 +55,31 @@ export const middlewareErrorHandler = (): ErrorRequestHandler => {
HTTP_HEADER.CONTENT_TYPE,
'application/json; charset=utf-8'
);
const error = { message: err.message, stack: err.stack };
res.json(error);
const sanitizedMessage = sanitizeErrorContent(err.message);
// expressデフォルトのエラーハンドラーのみerr.stackが空になるが、独自エラーハンドラーはerr.stackが表示されるので明示的に表示判断する
const sanitizedStack =
process.env.NODE_ENV !== 'production'
? sanitizeErrorContent(err.stack ?? '')
: undefined;
res.json({
message: sanitizedMessage,
...(sanitizedStack ? { stack: sanitizedStack } : {}),
});
break;
}
default:
res.setHeader(HTTP_HEADER.CONTENT_TYPE, 'text/plain; charset=utf-8');
res.send(stringify(err));
// expressデフォルトのエラーハンドラーのみerr.stackが空になるが、独自エラーハンドラーはerr.stackが表示されるので明示的に表示判断する
res.send(
sanitizeErrorContent(
JSON.stringify({
message: err.message,
...(process.env.NODE_ENV !== 'production'
? { stack: err.stack }
: {}),
})
)
);
break;
}
};
Expand Down
3 changes: 3 additions & 0 deletions example/nodejs/src/openapi/components.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ components:
description: Size of list
schema:
type: integer
minimum: 1
maximum: 100
required: false
PagerPageQueryParam:
name: page
in: query
description: Page number of list
schema:
type: integer
minimum: 1
required: false
SortQueryParam:
name: sort
Expand Down
3 changes: 3 additions & 0 deletions example/nodejs/src/openapi/resources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ paths:
parameters:
- $ref: '#/components/parameters/ResourceNamePathParam'
- $ref: '#/components/parameters/FormatQueryParam'
- $ref: './components.yaml#/components/parameters/PagerSizeQueryParam'
- $ref: './components.yaml#/components/parameters/PagerPageQueryParam'
- $ref: './components.yaml#/components/parameters/SortQueryParam'
responses:
200:
description: OK
Expand Down
Loading

0 comments on commit bec1cce

Please sign in to comment.