@apiposture/cli 1.0.1

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.
Files changed (110) hide show
  1. package/.apiposture.json.example +56 -0
  2. package/.github/workflows/publish.yml +38 -0
  3. package/.github/workflows/test.yml +42 -0
  4. package/LICENSE +21 -0
  5. package/README.md +156 -0
  6. package/dist/cli/commands/license/activate.d.ts +3 -0
  7. package/dist/cli/commands/license/activate.js +35 -0
  8. package/dist/cli/commands/license/deactivate.d.ts +3 -0
  9. package/dist/cli/commands/license/deactivate.js +28 -0
  10. package/dist/cli/commands/license/status.d.ts +3 -0
  11. package/dist/cli/commands/license/status.js +36 -0
  12. package/dist/cli/commands/scan.d.ts +3 -0
  13. package/dist/cli/commands/scan.js +211 -0
  14. package/dist/cli/options.d.ts +27 -0
  15. package/dist/cli/options.js +30 -0
  16. package/dist/core/analysis/project-analyzer.d.ts +16 -0
  17. package/dist/core/analysis/project-analyzer.js +54 -0
  18. package/dist/core/analysis/source-file-loader.d.ts +32 -0
  19. package/dist/core/analysis/source-file-loader.js +155 -0
  20. package/dist/core/authorization/authorization-extractor.d.ts +11 -0
  21. package/dist/core/authorization/authorization-extractor.js +2 -0
  22. package/dist/core/authorization/express-auth-extractor.d.ts +10 -0
  23. package/dist/core/authorization/express-auth-extractor.js +106 -0
  24. package/dist/core/authorization/global-auth-analyzer.d.ts +12 -0
  25. package/dist/core/authorization/global-auth-analyzer.js +74 -0
  26. package/dist/core/authorization/nestjs-auth-extractor.d.ts +13 -0
  27. package/dist/core/authorization/nestjs-auth-extractor.js +142 -0
  28. package/dist/core/configuration/config-loader.d.ts +27 -0
  29. package/dist/core/configuration/config-loader.js +72 -0
  30. package/dist/core/configuration/suppression-matcher.d.ts +14 -0
  31. package/dist/core/configuration/suppression-matcher.js +79 -0
  32. package/dist/core/discovery/discoverer-interface.d.ts +7 -0
  33. package/dist/core/discovery/discoverer-interface.js +2 -0
  34. package/dist/core/discovery/express-discoverer.d.ts +20 -0
  35. package/dist/core/discovery/express-discoverer.js +223 -0
  36. package/dist/core/discovery/fastify-discoverer.d.ts +19 -0
  37. package/dist/core/discovery/fastify-discoverer.js +249 -0
  38. package/dist/core/discovery/framework-detector.d.ts +9 -0
  39. package/dist/core/discovery/framework-detector.js +61 -0
  40. package/dist/core/discovery/index.d.ts +8 -0
  41. package/dist/core/discovery/index.js +8 -0
  42. package/dist/core/discovery/koa-discoverer.d.ts +16 -0
  43. package/dist/core/discovery/koa-discoverer.js +151 -0
  44. package/dist/core/discovery/nestjs-discoverer.d.ts +16 -0
  45. package/dist/core/discovery/nestjs-discoverer.js +180 -0
  46. package/dist/core/discovery/route-group-registry.d.ts +18 -0
  47. package/dist/core/discovery/route-group-registry.js +50 -0
  48. package/dist/core/licensing/license-context.d.ts +17 -0
  49. package/dist/core/licensing/license-context.js +15 -0
  50. package/dist/core/licensing/license-features.d.ts +14 -0
  51. package/dist/core/licensing/license-features.js +47 -0
  52. package/dist/core/models/authorization-info.d.ts +13 -0
  53. package/dist/core/models/authorization-info.js +25 -0
  54. package/dist/core/models/endpoint-type.d.ts +8 -0
  55. package/dist/core/models/endpoint-type.js +12 -0
  56. package/dist/core/models/endpoint.d.ts +16 -0
  57. package/dist/core/models/endpoint.js +16 -0
  58. package/dist/core/models/finding.d.ts +19 -0
  59. package/dist/core/models/finding.js +8 -0
  60. package/dist/core/models/http-method.d.ts +14 -0
  61. package/dist/core/models/http-method.js +25 -0
  62. package/dist/core/models/index.d.ts +10 -0
  63. package/dist/core/models/index.js +10 -0
  64. package/dist/core/models/scan-result.d.ts +21 -0
  65. package/dist/core/models/scan-result.js +35 -0
  66. package/dist/core/models/security-classification.d.ts +8 -0
  67. package/dist/core/models/security-classification.js +12 -0
  68. package/dist/core/models/severity.d.ts +11 -0
  69. package/dist/core/models/severity.js +23 -0
  70. package/dist/core/models/source-location.d.ts +7 -0
  71. package/dist/core/models/source-location.js +4 -0
  72. package/dist/index.d.ts +3 -0
  73. package/dist/index.js +23 -0
  74. package/dist/licensing/license-manager.d.ts +38 -0
  75. package/dist/licensing/license-manager.js +184 -0
  76. package/dist/output/accessibility-helper.d.ts +22 -0
  77. package/dist/output/accessibility-helper.js +98 -0
  78. package/dist/output/formatter-interface.d.ts +11 -0
  79. package/dist/output/formatter-interface.js +2 -0
  80. package/dist/output/index.d.ts +6 -0
  81. package/dist/output/index.js +6 -0
  82. package/dist/output/json-formatter.d.ts +7 -0
  83. package/dist/output/json-formatter.js +72 -0
  84. package/dist/output/markdown-formatter.d.ts +10 -0
  85. package/dist/output/markdown-formatter.js +114 -0
  86. package/dist/output/terminal-formatter.d.ts +12 -0
  87. package/dist/output/terminal-formatter.js +82 -0
  88. package/dist/rules/consistency/controller-action-conflict.d.ts +19 -0
  89. package/dist/rules/consistency/controller-action-conflict.js +40 -0
  90. package/dist/rules/consistency/missing-auth-on-writes.d.ts +21 -0
  91. package/dist/rules/consistency/missing-auth-on-writes.js +59 -0
  92. package/dist/rules/exposure/allow-anonymous-on-write.d.ts +20 -0
  93. package/dist/rules/exposure/allow-anonymous-on-write.js +42 -0
  94. package/dist/rules/exposure/public-without-explicit-intent.d.ts +20 -0
  95. package/dist/rules/exposure/public-without-explicit-intent.js +58 -0
  96. package/dist/rules/index.d.ts +11 -0
  97. package/dist/rules/index.js +11 -0
  98. package/dist/rules/privilege/excessive-role-access.d.ts +20 -0
  99. package/dist/rules/privilege/excessive-role-access.js +36 -0
  100. package/dist/rules/privilege/weak-role-naming.d.ts +20 -0
  101. package/dist/rules/privilege/weak-role-naming.js +50 -0
  102. package/dist/rules/rule-engine.d.ts +15 -0
  103. package/dist/rules/rule-engine.js +52 -0
  104. package/dist/rules/rule-interface.d.ts +16 -0
  105. package/dist/rules/rule-interface.js +2 -0
  106. package/dist/rules/surface/sensitive-route-keywords.d.ts +20 -0
  107. package/dist/rules/surface/sensitive-route-keywords.js +63 -0
  108. package/dist/rules/surface/unprotected-endpoint.d.ts +20 -0
  109. package/dist/rules/surface/unprotected-endpoint.js +61 -0
  110. package/package.json +60 -0
@@ -0,0 +1,30 @@
1
+ import { parseSecurityClassification } from '../core/models/security-classification.js';
2
+ import { parseHttpMethod } from '../core/models/http-method.js';
3
+ import { parseEndpointType } from '../core/models/endpoint-type.js';
4
+ export const defaultScanOptions = {
5
+ output: 'terminal',
6
+ noColor: false,
7
+ noIcons: false,
8
+ };
9
+ export function parseClassificationList(value) {
10
+ return value
11
+ .split(',')
12
+ .map((v) => parseSecurityClassification(v.trim()))
13
+ .filter((v) => v !== undefined);
14
+ }
15
+ export function parseMethodList(value) {
16
+ return value
17
+ .split(',')
18
+ .map((v) => parseHttpMethod(v.trim()))
19
+ .filter((v) => v !== undefined);
20
+ }
21
+ export function parseApiStyleList(value) {
22
+ return value
23
+ .split(',')
24
+ .map((v) => parseEndpointType(v.trim()))
25
+ .filter((v) => v !== undefined);
26
+ }
27
+ export function parseRuleList(value) {
28
+ return value.split(',').map((v) => v.trim().toUpperCase());
29
+ }
30
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1,16 @@
1
+ import { ScanResult } from '../models/scan-result.js';
2
+ import { EndpointDiscoverer } from '../discovery/discoverer-interface.js';
3
+ import { SecurityRule } from '../../rules/rule-interface.js';
4
+ export declare class ProjectAnalyzer {
5
+ private sourceLoader;
6
+ private discoverers;
7
+ private rules;
8
+ constructor();
9
+ registerDiscoverer(discoverer: EndpointDiscoverer): void;
10
+ registerRule(rule: SecurityRule): void;
11
+ registerRules(rules: SecurityRule[]): void;
12
+ analyze(projectPath: string): Promise<ScanResult>;
13
+ private discoverEndpoints;
14
+ private evaluateRules;
15
+ }
16
+ //# sourceMappingURL=project-analyzer.d.ts.map
@@ -0,0 +1,54 @@
1
+ import { createScanResult } from '../models/scan-result.js';
2
+ import { SourceFileLoader } from './source-file-loader.js';
3
+ export class ProjectAnalyzer {
4
+ sourceLoader;
5
+ discoverers = [];
6
+ rules = [];
7
+ constructor() {
8
+ this.sourceLoader = new SourceFileLoader();
9
+ }
10
+ registerDiscoverer(discoverer) {
11
+ this.discoverers.push(discoverer);
12
+ }
13
+ registerRule(rule) {
14
+ this.rules.push(rule);
15
+ }
16
+ registerRules(rules) {
17
+ this.rules.push(...rules);
18
+ }
19
+ async analyze(projectPath) {
20
+ const startTime = Date.now();
21
+ const sourceFiles = await this.sourceLoader.loadDirectory(projectPath);
22
+ const endpoints = await this.discoverEndpoints(sourceFiles);
23
+ const findings = this.evaluateRules(endpoints);
24
+ const scanDurationMs = Date.now() - startTime;
25
+ return createScanResult({
26
+ projectPath,
27
+ endpoints,
28
+ findings,
29
+ filesScanned: sourceFiles.length,
30
+ scanDurationMs,
31
+ });
32
+ }
33
+ async discoverEndpoints(sourceFiles) {
34
+ const endpoints = [];
35
+ for (const discoverer of this.discoverers) {
36
+ for (const file of sourceFiles) {
37
+ const discovered = await discoverer.discover(file);
38
+ endpoints.push(...discovered);
39
+ }
40
+ }
41
+ return endpoints;
42
+ }
43
+ evaluateRules(endpoints) {
44
+ const findings = [];
45
+ for (const rule of this.rules) {
46
+ for (const endpoint of endpoints) {
47
+ const ruleFindings = rule.evaluate(endpoint);
48
+ findings.push(...ruleFindings);
49
+ }
50
+ }
51
+ return findings;
52
+ }
53
+ }
54
+ //# sourceMappingURL=project-analyzer.js.map
@@ -0,0 +1,32 @@
1
+ import * as ts from 'typescript';
2
+ export interface LoadedSourceFile {
3
+ filePath: string;
4
+ sourceFile: ts.SourceFile;
5
+ content: string;
6
+ }
7
+ export interface SourceFileLoaderOptions {
8
+ extensions?: string[];
9
+ excludePatterns?: string[];
10
+ }
11
+ export declare class SourceFileLoader {
12
+ private options;
13
+ private cache;
14
+ constructor(options?: SourceFileLoaderOptions);
15
+ loadDirectory(dirPath: string): Promise<LoadedSourceFile[]>;
16
+ loadFile(filePath: string): Promise<LoadedSourceFile | null>;
17
+ private getScriptKind;
18
+ clearCache(): void;
19
+ }
20
+ export declare function getLineAndColumn(sourceFile: ts.SourceFile, position: number): {
21
+ line: number;
22
+ column: number;
23
+ };
24
+ export declare function getNodeText(node: ts.Node, sourceFile: ts.SourceFile): string;
25
+ export declare function findNodes<T extends ts.Node>(node: ts.Node, predicate: (node: ts.Node) => node is T): T[];
26
+ export declare function findNodesOfKind<T extends ts.Node>(node: ts.Node, kind: ts.SyntaxKind): T[];
27
+ export declare function getDecorators(node: ts.Node): ts.Decorator[];
28
+ export declare function getDecoratorName(decorator: ts.Decorator): string | null;
29
+ export declare function getDecoratorArguments(decorator: ts.Decorator): ts.Expression[];
30
+ export declare function getStringLiteralValue(node: ts.Node): string | null;
31
+ export declare function getArrayLiteralElements(node: ts.Node): ts.Expression[];
32
+ //# sourceMappingURL=source-file-loader.d.ts.map
@@ -0,0 +1,155 @@
1
+ import * as ts from 'typescript';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { glob } from 'glob';
5
+ const defaultOptions = {
6
+ extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'],
7
+ excludePatterns: [
8
+ '**/node_modules/**',
9
+ '**/dist/**',
10
+ '**/build/**',
11
+ '**/.git/**',
12
+ '**/coverage/**',
13
+ '**/*.spec.ts',
14
+ '**/*.test.ts',
15
+ '**/*.spec.js',
16
+ '**/*.test.js',
17
+ ],
18
+ };
19
+ export class SourceFileLoader {
20
+ options;
21
+ cache = new Map();
22
+ constructor(options = {}) {
23
+ this.options = { ...defaultOptions, ...options };
24
+ }
25
+ async loadDirectory(dirPath) {
26
+ const absolutePath = path.resolve(dirPath);
27
+ if (!fs.existsSync(absolutePath)) {
28
+ throw new Error(`Directory not found: ${absolutePath}`);
29
+ }
30
+ const patterns = this.options.extensions.map((ext) => `**/*${ext}`);
31
+ const files = [];
32
+ for (const pattern of patterns) {
33
+ const matches = await glob(pattern, {
34
+ cwd: absolutePath,
35
+ ignore: this.options.excludePatterns,
36
+ absolute: true,
37
+ nodir: true,
38
+ });
39
+ files.push(...matches);
40
+ }
41
+ const uniqueFiles = [...new Set(files)];
42
+ const loadedFiles = [];
43
+ for (const filePath of uniqueFiles) {
44
+ try {
45
+ const loaded = await this.loadFile(filePath);
46
+ if (loaded) {
47
+ loadedFiles.push(loaded);
48
+ }
49
+ }
50
+ catch (error) {
51
+ // Skip files that can't be loaded/parsed
52
+ console.warn(`Warning: Could not load ${filePath}: ${error}`);
53
+ }
54
+ }
55
+ return loadedFiles;
56
+ }
57
+ async loadFile(filePath) {
58
+ const absolutePath = path.resolve(filePath);
59
+ if (this.cache.has(absolutePath)) {
60
+ return this.cache.get(absolutePath);
61
+ }
62
+ if (!fs.existsSync(absolutePath)) {
63
+ return null;
64
+ }
65
+ const content = fs.readFileSync(absolutePath, 'utf-8');
66
+ const sourceFile = ts.createSourceFile(absolutePath, content, ts.ScriptTarget.Latest, true, this.getScriptKind(absolutePath));
67
+ const loaded = {
68
+ filePath: absolutePath,
69
+ sourceFile,
70
+ content,
71
+ };
72
+ this.cache.set(absolutePath, loaded);
73
+ return loaded;
74
+ }
75
+ getScriptKind(filePath) {
76
+ const ext = path.extname(filePath).toLowerCase();
77
+ switch (ext) {
78
+ case '.ts':
79
+ return ts.ScriptKind.TS;
80
+ case '.tsx':
81
+ return ts.ScriptKind.TSX;
82
+ case '.js':
83
+ case '.mjs':
84
+ case '.cjs':
85
+ return ts.ScriptKind.JS;
86
+ case '.jsx':
87
+ return ts.ScriptKind.JSX;
88
+ default:
89
+ return ts.ScriptKind.Unknown;
90
+ }
91
+ }
92
+ clearCache() {
93
+ this.cache.clear();
94
+ }
95
+ }
96
+ export function getLineAndColumn(sourceFile, position) {
97
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(position);
98
+ return { line: line + 1, column: character + 1 };
99
+ }
100
+ export function getNodeText(node, sourceFile) {
101
+ return node.getText(sourceFile);
102
+ }
103
+ export function findNodes(node, predicate) {
104
+ const results = [];
105
+ function visit(n) {
106
+ if (predicate(n)) {
107
+ results.push(n);
108
+ }
109
+ ts.forEachChild(n, visit);
110
+ }
111
+ visit(node);
112
+ return results;
113
+ }
114
+ export function findNodesOfKind(node, kind) {
115
+ return findNodes(node, (n) => n.kind === kind);
116
+ }
117
+ export function getDecorators(node) {
118
+ if (ts.canHaveDecorators(node)) {
119
+ return (ts.getDecorators(node) ?? []);
120
+ }
121
+ return [];
122
+ }
123
+ export function getDecoratorName(decorator) {
124
+ const expression = decorator.expression;
125
+ if (ts.isIdentifier(expression)) {
126
+ return expression.text;
127
+ }
128
+ if (ts.isCallExpression(expression)) {
129
+ const callee = expression.expression;
130
+ if (ts.isIdentifier(callee)) {
131
+ return callee.text;
132
+ }
133
+ }
134
+ return null;
135
+ }
136
+ export function getDecoratorArguments(decorator) {
137
+ const expression = decorator.expression;
138
+ if (ts.isCallExpression(expression)) {
139
+ return [...expression.arguments];
140
+ }
141
+ return [];
142
+ }
143
+ export function getStringLiteralValue(node) {
144
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
145
+ return node.text;
146
+ }
147
+ return null;
148
+ }
149
+ export function getArrayLiteralElements(node) {
150
+ if (ts.isArrayLiteralExpression(node)) {
151
+ return [...node.elements];
152
+ }
153
+ return [];
154
+ }
155
+ //# sourceMappingURL=source-file-loader.js.map
@@ -0,0 +1,11 @@
1
+ import { AuthorizationInfo } from '../models/authorization-info.js';
2
+ export interface AuthorizationExtractorContext {
3
+ isRouter?: boolean;
4
+ routerMiddlewares?: string[];
5
+ classGuards?: string[];
6
+ classRoles?: string[];
7
+ }
8
+ export interface AuthorizationExtractor {
9
+ extract(middlewares: string[], context?: AuthorizationExtractorContext): AuthorizationInfo;
10
+ }
11
+ //# sourceMappingURL=authorization-extractor.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=authorization-extractor.js.map
@@ -0,0 +1,10 @@
1
+ import { AuthorizationInfo } from '../models/authorization-info.js';
2
+ import { AuthorizationExtractor, AuthorizationExtractorContext } from './authorization-extractor.js';
3
+ export declare class ExpressAuthExtractor implements AuthorizationExtractor {
4
+ extract(middlewares: string[], context?: AuthorizationExtractorContext): AuthorizationInfo;
5
+ private processMiddleware;
6
+ private matchesPatterns;
7
+ private containsAuthKeyword;
8
+ private extractRolesFromMiddleware;
9
+ }
10
+ //# sourceMappingURL=express-auth-extractor.d.ts.map
@@ -0,0 +1,106 @@
1
+ import { createDefaultAuthorizationInfo, determineClassification, } from '../models/authorization-info.js';
2
+ // Common authentication middleware patterns
3
+ const AUTH_MIDDLEWARE_PATTERNS = [
4
+ /^passport\.authenticate/i,
5
+ /^jwt$/i,
6
+ /^expressjwt$/i,
7
+ /^requireAuth$/i,
8
+ /^ensureAuthenticated$/i,
9
+ /^isAuthenticated$/i,
10
+ /^authenticate$/i,
11
+ /^authMiddleware$/i,
12
+ /^checkAuth$/i,
13
+ /^verifyToken$/i,
14
+ /^validateToken$/i,
15
+ /^bearerToken$/i,
16
+ /^auth$/i,
17
+ /^protected$/i,
18
+ ];
19
+ // Common explicit public/anonymous patterns
20
+ const PUBLIC_PATTERNS = [
21
+ /^allowAnonymous$/i,
22
+ /^public$/i,
23
+ /^skipAuth$/i,
24
+ /^noAuth$/i,
25
+ /^optional$/i,
26
+ ];
27
+ // Role-based middleware patterns
28
+ const ROLE_PATTERNS = [
29
+ /^requireRole$/i,
30
+ /^hasRole$/i,
31
+ /^roles$/i,
32
+ /^checkRole$/i,
33
+ /^authorize$/i,
34
+ /^can$/i,
35
+ /^permit$/i,
36
+ ];
37
+ export class ExpressAuthExtractor {
38
+ extract(middlewares, context) {
39
+ const allMiddlewares = [
40
+ ...(context?.routerMiddlewares ?? []),
41
+ ...middlewares,
42
+ ];
43
+ const auth = createDefaultAuthorizationInfo();
44
+ auth.middlewareChain = allMiddlewares;
45
+ for (const middleware of allMiddlewares) {
46
+ this.processMiddleware(middleware, auth);
47
+ }
48
+ auth.classification = determineClassification(auth);
49
+ return auth;
50
+ }
51
+ processMiddleware(middleware, auth) {
52
+ const normalizedName = middleware.trim();
53
+ // Check for explicit public markers
54
+ if (this.matchesPatterns(normalizedName, PUBLIC_PATTERNS)) {
55
+ auth.isExplicitlyPublic = true;
56
+ return;
57
+ }
58
+ // Check for authentication middleware
59
+ if (this.matchesPatterns(normalizedName, AUTH_MIDDLEWARE_PATTERNS)) {
60
+ auth.isAuthenticated = true;
61
+ auth.guardNames.push(normalizedName);
62
+ return;
63
+ }
64
+ // Check for role-based middleware
65
+ if (this.matchesPatterns(normalizedName, ROLE_PATTERNS)) {
66
+ auth.isAuthenticated = true;
67
+ // Try to extract role names from common patterns
68
+ const roles = this.extractRolesFromMiddleware(normalizedName);
69
+ auth.roles.push(...roles);
70
+ return;
71
+ }
72
+ // Check for common auth patterns in the middleware name
73
+ if (this.containsAuthKeyword(normalizedName)) {
74
+ auth.isAuthenticated = true;
75
+ auth.guardNames.push(normalizedName);
76
+ }
77
+ }
78
+ matchesPatterns(name, patterns) {
79
+ // Get just the function/method name (strip object prefix)
80
+ const funcName = name.includes('.') ? name.split('.').pop() : name;
81
+ return patterns.some((pattern) => pattern.test(funcName));
82
+ }
83
+ containsAuthKeyword(name) {
84
+ const keywords = ['auth', 'jwt', 'token', 'session', 'login', 'secure'];
85
+ const lower = name.toLowerCase();
86
+ return keywords.some((kw) => lower.includes(kw));
87
+ }
88
+ extractRolesFromMiddleware(middleware) {
89
+ // Try to extract roles from patterns like:
90
+ // requireRole('admin'), hasRole(['user', 'admin']), roles('manager')
91
+ const roleMatch = middleware.match(/\(['"]?([^'")\]]+)['"]?\]/i);
92
+ if (roleMatch) {
93
+ return roleMatch[1].split(',').map((r) => r.trim().replace(/['"]/g, ''));
94
+ }
95
+ // For patterns like authorize.admin or can.read
96
+ if (middleware.includes('.')) {
97
+ const parts = middleware.split('.');
98
+ const lastPart = parts[parts.length - 1];
99
+ if (!this.matchesPatterns(lastPart, AUTH_MIDDLEWARE_PATTERNS)) {
100
+ return [lastPart];
101
+ }
102
+ }
103
+ return [];
104
+ }
105
+ }
106
+ //# sourceMappingURL=express-auth-extractor.js.map
@@ -0,0 +1,12 @@
1
+ import { LoadedSourceFile } from '../analysis/source-file-loader.js';
2
+ export interface GlobalAuthConfig {
3
+ hasGlobalGuard: boolean;
4
+ globalGuardName?: string;
5
+ hasGlobalPrefix: boolean;
6
+ globalPrefix?: string;
7
+ }
8
+ export declare class GlobalAuthAnalyzer {
9
+ analyze(files: LoadedSourceFile[]): GlobalAuthConfig;
10
+ private analyzeFile;
11
+ }
12
+ //# sourceMappingURL=global-auth-analyzer.d.ts.map
@@ -0,0 +1,74 @@
1
+ import * as ts from 'typescript';
2
+ import { findNodes } from '../analysis/source-file-loader.js';
3
+ export class GlobalAuthAnalyzer {
4
+ analyze(files) {
5
+ const config = {
6
+ hasGlobalGuard: false,
7
+ hasGlobalPrefix: false,
8
+ };
9
+ for (const file of files) {
10
+ this.analyzeFile(file, config);
11
+ }
12
+ return config;
13
+ }
14
+ analyzeFile(file, config) {
15
+ const { sourceFile } = file;
16
+ // Look for app.useGlobalGuards() calls
17
+ const callExpressions = findNodes(sourceFile, ts.isCallExpression);
18
+ for (const callExpr of callExpressions) {
19
+ // Check for app.useGlobalGuards(new AuthGuard())
20
+ if (ts.isPropertyAccessExpression(callExpr.expression)) {
21
+ const propAccess = callExpr.expression;
22
+ const methodName = propAccess.name.text;
23
+ if (methodName === 'useGlobalGuards') {
24
+ config.hasGlobalGuard = true;
25
+ // Try to extract guard name
26
+ if (callExpr.arguments.length > 0) {
27
+ const arg = callExpr.arguments[0];
28
+ if (ts.isNewExpression(arg) && ts.isIdentifier(arg.expression)) {
29
+ config.globalGuardName = arg.expression.text;
30
+ }
31
+ }
32
+ }
33
+ if (methodName === 'setGlobalPrefix') {
34
+ config.hasGlobalPrefix = true;
35
+ if (callExpr.arguments.length > 0) {
36
+ const arg = callExpr.arguments[0];
37
+ if (ts.isStringLiteral(arg)) {
38
+ config.globalPrefix = arg.text;
39
+ }
40
+ }
41
+ }
42
+ }
43
+ // Check for APP_GUARD provider pattern
44
+ // { provide: APP_GUARD, useClass: AuthGuard }
45
+ if (callExpr.arguments.length > 0 &&
46
+ ts.isObjectLiteralExpression(callExpr.arguments[0])) {
47
+ const objLiteral = callExpr.arguments[0];
48
+ let hasAppGuard = false;
49
+ let guardName;
50
+ for (const prop of objLiteral.properties) {
51
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
52
+ if (prop.name.text === 'provide') {
53
+ if (ts.isIdentifier(prop.initializer)) {
54
+ if (prop.initializer.text === 'APP_GUARD') {
55
+ hasAppGuard = true;
56
+ }
57
+ }
58
+ }
59
+ if (prop.name.text === 'useClass') {
60
+ if (ts.isIdentifier(prop.initializer)) {
61
+ guardName = prop.initializer.text;
62
+ }
63
+ }
64
+ }
65
+ }
66
+ if (hasAppGuard && guardName) {
67
+ config.hasGlobalGuard = true;
68
+ config.globalGuardName = guardName;
69
+ }
70
+ }
71
+ }
72
+ }
73
+ }
74
+ //# sourceMappingURL=global-auth-analyzer.js.map
@@ -0,0 +1,13 @@
1
+ import * as ts from 'typescript';
2
+ import { AuthorizationInfo } from '../models/authorization-info.js';
3
+ export interface NestJSAuthContext {
4
+ classGuards?: string[];
5
+ classRoles?: string[];
6
+ isPublic?: boolean;
7
+ }
8
+ export declare class NestJSAuthExtractor {
9
+ extract(decorators: ts.Decorator[], context?: NestJSAuthContext): AuthorizationInfo;
10
+ private processDecorator;
11
+ private isAuthGuard;
12
+ }
13
+ //# sourceMappingURL=nestjs-auth-extractor.d.ts.map
@@ -0,0 +1,142 @@
1
+ import * as ts from 'typescript';
2
+ import { createDefaultAuthorizationInfo, determineClassification, } from '../models/authorization-info.js';
3
+ import { getDecoratorName, getDecoratorArguments, getStringLiteralValue, getArrayLiteralElements, } from '../analysis/source-file-loader.js';
4
+ // Auth guard patterns
5
+ const AUTH_GUARD_PATTERNS = [
6
+ /AuthGuard/i,
7
+ /JwtAuthGuard/i,
8
+ /LocalAuthGuard/i,
9
+ /SessionGuard/i,
10
+ /BearerGuard/i,
11
+ /TokenGuard/i,
12
+ ];
13
+ // Public decorator names
14
+ const PUBLIC_DECORATORS = new Set([
15
+ 'Public',
16
+ 'AllowAnonymous',
17
+ 'SkipAuth',
18
+ 'NoAuth',
19
+ 'IsPublic',
20
+ ]);
21
+ // Role decorator names
22
+ const ROLE_DECORATORS = new Set(['Roles', 'RequireRoles', 'HasRoles', 'Authorize']);
23
+ // Policy decorator names
24
+ const POLICY_DECORATORS = new Set(['Policies', 'RequirePolicies', 'CheckPolicies']);
25
+ export class NestJSAuthExtractor {
26
+ extract(decorators, context) {
27
+ const auth = createDefaultAuthorizationInfo();
28
+ // Apply class-level auth first
29
+ if (context?.classGuards) {
30
+ for (const guard of context.classGuards) {
31
+ if (this.isAuthGuard(guard)) {
32
+ auth.isAuthenticated = true;
33
+ auth.guardNames.push(guard);
34
+ }
35
+ }
36
+ }
37
+ if (context?.classRoles) {
38
+ auth.roles.push(...context.classRoles);
39
+ if (context.classRoles.length > 0) {
40
+ auth.isAuthenticated = true;
41
+ }
42
+ }
43
+ if (context?.isPublic) {
44
+ auth.isExplicitlyPublic = true;
45
+ }
46
+ // Process method-level decorators (can override class-level)
47
+ for (const decorator of decorators) {
48
+ this.processDecorator(decorator, auth);
49
+ }
50
+ // Method-level @Public overrides class-level guards
51
+ const hasMethodPublic = decorators.some((d) => {
52
+ const name = getDecoratorName(d);
53
+ return name && PUBLIC_DECORATORS.has(name);
54
+ });
55
+ if (hasMethodPublic) {
56
+ auth.isExplicitlyPublic = true;
57
+ // Don't clear isAuthenticated as it creates the AP003 conflict scenario
58
+ }
59
+ auth.classification = determineClassification(auth);
60
+ return auth;
61
+ }
62
+ processDecorator(decorator, auth) {
63
+ const name = getDecoratorName(decorator);
64
+ if (!name)
65
+ return;
66
+ // Check for @Public or similar
67
+ if (PUBLIC_DECORATORS.has(name)) {
68
+ auth.isExplicitlyPublic = true;
69
+ return;
70
+ }
71
+ // Check for @UseGuards
72
+ if (name === 'UseGuards') {
73
+ const args = getDecoratorArguments(decorator);
74
+ for (const arg of args) {
75
+ if (ts.isIdentifier(arg)) {
76
+ const guardName = arg.text;
77
+ if (this.isAuthGuard(guardName)) {
78
+ auth.isAuthenticated = true;
79
+ }
80
+ auth.guardNames.push(guardName);
81
+ }
82
+ // Handle UseGuards(AuthGuard('jwt'))
83
+ if (ts.isCallExpression(arg)) {
84
+ if (ts.isIdentifier(arg.expression)) {
85
+ const guardName = arg.expression.text;
86
+ if (this.isAuthGuard(guardName)) {
87
+ auth.isAuthenticated = true;
88
+ }
89
+ auth.guardNames.push(guardName);
90
+ }
91
+ }
92
+ }
93
+ return;
94
+ }
95
+ // Check for @Roles
96
+ if (ROLE_DECORATORS.has(name)) {
97
+ const args = getDecoratorArguments(decorator);
98
+ for (const arg of args) {
99
+ // @Roles('admin', 'user')
100
+ const value = getStringLiteralValue(arg);
101
+ if (value) {
102
+ auth.roles.push(value);
103
+ auth.isAuthenticated = true;
104
+ }
105
+ // @Roles(['admin', 'user'])
106
+ const arrayElements = getArrayLiteralElements(arg);
107
+ for (const elem of arrayElements) {
108
+ const elemValue = getStringLiteralValue(elem);
109
+ if (elemValue) {
110
+ auth.roles.push(elemValue);
111
+ auth.isAuthenticated = true;
112
+ }
113
+ }
114
+ }
115
+ return;
116
+ }
117
+ // Check for @Policies
118
+ if (POLICY_DECORATORS.has(name)) {
119
+ const args = getDecoratorArguments(decorator);
120
+ for (const arg of args) {
121
+ const value = getStringLiteralValue(arg);
122
+ if (value) {
123
+ auth.policies.push(value);
124
+ auth.isAuthenticated = true;
125
+ }
126
+ const arrayElements = getArrayLiteralElements(arg);
127
+ for (const elem of arrayElements) {
128
+ const elemValue = getStringLiteralValue(elem);
129
+ if (elemValue) {
130
+ auth.policies.push(elemValue);
131
+ auth.isAuthenticated = true;
132
+ }
133
+ }
134
+ }
135
+ return;
136
+ }
137
+ }
138
+ isAuthGuard(guardName) {
139
+ return AUTH_GUARD_PATTERNS.some((pattern) => pattern.test(guardName));
140
+ }
141
+ }
142
+ //# sourceMappingURL=nestjs-auth-extractor.js.map