Skip to content

ACHOO-146: AuthZ#28

Closed
jbickar wants to merge 12 commits intoACHOO-130-node-samlfrom
ACHOO-146
Closed

ACHOO-146: AuthZ#28
jbickar wants to merge 12 commits intoACHOO-130-node-samlfrom
ACHOO-146

Conversation

@jbickar
Copy link
Contributor

@jbickar jbickar commented Dec 10, 2025

NOT READY FOR REVIEW

  • (Edit the above to reflect status)

Summary

  • TL;DR - what's this PR for?

Review By (Date)

  • When does this need to be reviewed by?

Criticality

  • How critical is this PR on a 1-10 scale? Also see Severity Assessment.
  • E.g., it affects one site, or every site and product?

Review Tasks

Setup tasks and/or behavior to test

  1. Check out this branch
  2. Navigate to...
  3. Verify...

Front End Validation

  • Is the markup using the appropriate semantic tags and passes HTML validation?
  • Cross-browser testing has been performed?
  • Automated accessibility scans performed?
  • Manual accessibility tests performed?
  • Design is approved by @ user?

Backend / Functional Validation

Code

  • Are the naming conventions following our standards?
  • Does the code have sufficient inline comments?
  • Is there anything in this code that would be hidden or hard to discover through the UI?
  • Are there any code smells?
  • Are tests provided? eg (unit, behat, or codeception)

Code security

General

  • Is there anything included in this PR that is not related to the problem it is trying to solve?
  • Is the approach to the problem appropriate?

Affected Projects or Products

  • Does this PR impact any particular projects, products, or modules?

Associated Issues and/or People

  • JIRA ticket(s)
  • Other PRs
  • Any other contextual information that might be helpful (e.g., description of a bug that this PR fixes, new functionality that it adds, etc.)
  • Anyone who should be notified? (@mention them here)

Resources

Copilot AI review requested due to automatic review settings December 10, 2025 22:41
@vercel
Copy link

vercel bot commented Dec 10, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
churro Ready Ready Preview Comment Dec 10, 2025 11:58pm

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a comprehensive authorization system (AuthZ) for the CHURRO application, building on top of the existing SAML authentication. The implementation adds a two-tier access control mechanism using eduPersonEntitlement values for global access and SUNet ID mappings for per-application access.

Key Changes:

  • Added authorization enforcement in middleware for dashboard and application routes
  • Implemented API-level authorization helpers and wrappers for protecting API endpoints
  • Created authorization utility functions for access control checks based on user entitlements and mappings

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
middleware.ts Added authorization checks for dashboard and application routes, enforcing access rules based on user permissions
lib/auth-utils.ts New authorization utilities including global access checks, per-application access validation, and environment variable parsing
lib/api-auth.ts New API authorization helpers with withApiAuthorization wrapper for protecting API routes
app/applications/[uuid]/page.tsx Added authorization error handling for API responses with user-friendly error displays
app/api/acquia/visits/route.ts Wrapped API handler with withApiAuthorization for authentication enforcement
app/api/acquia/views/route.ts Wrapped API handler with withApiAuthorization for authentication enforcement
app/api/acquia/applications/route.ts Wrapped API handler with withApiAuthorization for authentication enforcement
app/api/auth/logout/route.ts Added support for redirect parameter to allow redirecting to specific pages after logout
docs/SAML.md Added comprehensive documentation for the authorization system including configuration examples and access control flow
.github/copilot-instructions.md Updated with authorization system details and usage patterns
README.md Added authorization environment variables documentation
.env.example Added example authorization configuration variables
.gitignore Added .cache directory to gitignore
app/auth/test/page.tsx Updated logout link to redirect back to test page
Comments suppressed due to low confidence (2)

lib/auth-utils.ts:89

  • Missing newline between function declarations. This appears to be a formatting issue where the closing brace and JSDoc comment are merged together without proper spacing.
    const response = await fetch('/api/auth/status')

lib/auth-utils.ts:127

  • Missing newline between function declarations. This appears to be a formatting issue where the closing brace and JSDoc comment are merged together without proper spacing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

import { withApiAuthorization } from '@/lib/api-auth';

export async function GET(request: NextRequest) {
return withApiAuthorization(async (request: NextRequest, context: { user: any }) => {
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation. The code inside the withApiAuthorization callback is not properly indented. The block from line 7 onwards should be indented to match the callback function nesting level for better code readability.

Copilot uses AI. Check for mistakes.
return {
authorized: false,
user,
error: `Access denied. You do not have permission to access application ${applicationUuid}.`
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exposing the application UUID in the error message could be a security concern. Users who don't have access to an application shouldn't necessarily see its internal UUID. Consider using a more generic error message like "Access denied. You do not have permission to access this application."

Suggested change
error: `Access denied. You do not have permission to access application ${applicationUuid}.`
error: 'Access denied. You do not have permission to access this application.'

Copilot uses AI. Check for mistakes.
import { withApiAuthorization } from '@/lib/api-auth';

export async function GET(request: NextRequest) {
return withApiAuthorization(async (request: NextRequest, context: { user: any }) => {
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API is protected with withApiAuthorization but doesn't verify that the user has access to the specific application identified by subscriptionUuid. This means any authenticated user can access data for any application. Consider using requireApplicationUuid: true option or manually checking application access using the subscriptionUuid parameter.

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +26
return withApiAuthorization(async (request: NextRequest, context: { user: any }) => {
const searchParams = request.nextUrl.searchParams;
const subscriptionUuid = searchParams.get('subscriptionUuid');
const from = searchParams.get('from');
const to = searchParams.get('to');
const resolution = searchParams.get('resolution'); // Get granularity for daily data
/**
console.log('🚀 Views by Application API Route called with params:', {
subscriptionUuid,
from,
to,
resolution
});
*/
if (!subscriptionUuid) {
console.error('❌ Missing required parameter: subscriptionUuid');
return NextResponse.json(
{ error: 'subscriptionUuid is required' },
{ status: 400 }
);
}
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation. The code inside the withApiAuthorization callback is not properly indented, making it harder to read and maintain. The entire block from line 7-86 should be indented to match the callback function nesting level.

Copilot uses AI. Check for mistakes.
- Have full access to all API endpoints

#### Per-Application Access Users
- Can access dashboard (shows only their authorized applications)
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation states per-application users "Can access dashboard (shows only their authorized applications)", but the implementation in hasDashboardAccess function only grants access to users with global access. This means per-app users will receive a 403 error when trying to access the dashboard, contradicting what's documented here.

Suggested change
- Can access dashboard (shows only their authorized applications)
- Cannot access dashboard (will receive 403 Forbidden)

Copilot uses AI. Check for mistakes.
import { withApiAuthorization } from '@/lib/api-auth';

export async function GET(request: NextRequest) {
return withApiAuthorization(async (request: NextRequest, context: { user: any }) => {
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The applications API returns all applications for any authenticated user without filtering based on their permissions. Users with per-application access should only see applications they're authorized to access. The user context from withApiAuthorization is available but not being used to filter results. Consider filtering the applications list based on the user's permissions using getUserApplicationAccess() or hasApplicationAccess().

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +23
// Validate that redirect is to a safe path (starts with /)
if (redirectTo.startsWith('/')) {
return NextResponse.redirect(new URL(redirectTo, baseUrl))
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The redirect validation only checks if the path starts with '/', but this is insufficient to prevent open redirect vulnerabilities. An attacker could use URLs like '//evil.com' or '/\evil.com' which start with '/' but redirect to external sites. Use URL parsing and validate that the hostname matches the expected domain, or use a whitelist of allowed redirect paths.

Suggested change
// Validate that redirect is to a safe path (starts with /)
if (redirectTo.startsWith('/')) {
return NextResponse.redirect(new URL(redirectTo, baseUrl))
// Validate that redirectTo is a safe, relative path (no protocol, no hostname, no double slashes, no backslashes)
try {
// Only allow relative paths that start with a single slash and do not contain suspicious patterns
if (
redirectTo.startsWith('/') &&
!redirectTo.startsWith('//') &&
!redirectTo.startsWith('/\\') &&
!redirectTo.includes('\\') &&
!redirectTo.includes('..') // Prevent directory traversal
) {
const url = new URL(redirectTo, baseUrl)
// Ensure the redirect stays on the same origin
if (url.origin === baseUrl) {
return NextResponse.redirect(url)
}
}
} catch (e) {
// Ignore invalid URLs and fall through to default redirect

Copilot uses AI. Check for mistakes.
**Key Components** (`lib/auth-utils.ts`):
- `hasGlobalAccess(user)` - Check if user has global entitlement
- `hasApplicationAccess(user, uuid)` - Check specific app access
- `hasDashboardAccess(user)` - Check if can access dashboard (global OR any app)
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation states that dashboard access requires "global OR any app" access, but the implementation in hasDashboardAccess only grants access to users with global access. This inconsistency means per-application users will be denied dashboard access despite documentation stating they should have it.

Either update the implementation to allow per-app users to access the dashboard (showing only their authorized applications), or update the documentation to accurately reflect that only global access users can access the dashboard.

Suggested change
- `hasDashboardAccess(user)` - Check if can access dashboard (global OR any app)
- `hasDashboardAccess(user)` - Returns `true` if the user has global access **or** access to at least one application. Per-application users will see only their authorized applications on the dashboard.

Copilot uses AI. Check for mistakes.
requestHeaders.set('x-user-id', payload.id);
if (payload.sunetId) {
requestHeaders.set('x-user-sunetid', payload.sunetId);
requestHeaders.set('x-user-id', user.id);
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message says "access this application" but this is the dashboard route, not an application. The error message should be "You do not have permission to access the dashboard." to be more accurate and helpful to users.

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +64
* @param request NextRequest object
* @param applicationUuid Application UUID to check access for
* @returns Promise<{authorized: boolean, user: SamlUser | null, error?: string}>
*/
export async function checkApplicationApiAuthorization(
request: NextRequest,
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The request parameter is unused in this function. Since this is an authorization check based on user identity from cookies, the request parameter is not needed and can be removed from the function signature.

Suggested change
* @param request NextRequest object
* @param applicationUuid Application UUID to check access for
* @returns Promise<{authorized: boolean, user: SamlUser | null, error?: string}>
*/
export async function checkApplicationApiAuthorization(
request: NextRequest,
* @param applicationUuid Application UUID to check access for
* @returns Promise<{authorized: boolean, user: SamlUser | null, error?: string}>
*/
export async function checkApplicationApiAuthorization(

Copilot uses AI. Check for mistakes.
@jbickar
Copy link
Contributor Author

jbickar commented Dec 11, 2025

Closing this because I created it before necessary changes were made and the Copilot automatic review is just cluttering things up.

@jbickar jbickar closed this Dec 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant