-
Notifications
You must be signed in to change notification settings - Fork 41
Auto-register template creators as referrers in echo-start #674
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
base: master
Are you sure you want to change the base?
Auto-register template creators as referrers in echo-start #674
Conversation
|
@Trynax is attempting to deploy a commit to the Merit Systems Team on Vercel. A member of the Team first needs to authorize it. |
| export async function POST(request: NextRequest) { | ||
| try { | ||
| const body = await request.json(); | ||
| const validatedData = registerTemplateReferralSchema.parse(body); | ||
| const { appId, githubUsername, templateUrl } = validatedData; | ||
|
|
||
| const app = await db.echoApp.findUnique({ | ||
| where: { id: appId }, | ||
| include: { | ||
| appMemberships: { | ||
| where: { role: AppRole.OWNER }, | ||
| take: 1, | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| if (!app || app.appMemberships.length === 0) { | ||
| return NextResponse.json( | ||
| { success: false, message: 'App not found' }, | ||
| { status: 404 } | ||
| ); | ||
| } | ||
|
|
||
| const userId = app.appMemberships[0].userId; | ||
|
|
||
| const result = await registerTemplateReferral(userId, { | ||
| appId, | ||
| githubUsername, | ||
| templateUrl, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The /api/v1/referrals/register-template endpoint lacks authentication and accepts any appId, allowing anyone to set arbitrary referrers for any app in the system. This is a critical authorization bypass.
View Details
📝 Patch Details
diff --git a/packages/app/control/src/app/api/v1/referrals/register-template/route.ts b/packages/app/control/src/app/api/v1/referrals/register-template/route.ts
index 5a855111..51b14eba 100644
--- a/packages/app/control/src/app/api/v1/referrals/register-template/route.ts
+++ b/packages/app/control/src/app/api/v1/referrals/register-template/route.ts
@@ -1,76 +1,65 @@
-import { NextResponse, NextRequest } from 'next/server';
+import { NextResponse } from 'next/server';
import {
registerTemplateReferral,
registerTemplateReferralSchema,
} from '@/services/db/apps/template-referral';
-import { db } from '@/services/db/client';
-import { AppRole } from '@/services/db/apps/permissions/types';
+import { authRoute } from '../../../../../lib/api/auth-route';
-export async function POST(request: NextRequest) {
- try {
- const body = await request.json();
- const validatedData = registerTemplateReferralSchema.parse(body);
- const { appId, githubUsername, templateUrl } = validatedData;
+export const POST = authRoute
+ .body(registerTemplateReferralSchema)
+ .handler(async (_, context) => {
+ try {
+ const { appId, githubUsername, templateUrl } = context.body;
+ const { userId, appId: contextAppId } = context.ctx;
- const app = await db.echoApp.findUnique({
- where: { id: appId },
- include: {
- appMemberships: {
- where: { role: AppRole.OWNER },
- take: 1,
- },
- },
- });
-
- if (!app || app.appMemberships.length === 0) {
- return NextResponse.json(
- { success: false, message: 'App not found' },
- { status: 404 }
- );
- }
+ // Verify that the authenticated user owns the app they're trying to modify
+ if (contextAppId !== appId) {
+ return NextResponse.json(
+ { success: false, message: 'Access denied: You can only modify referrals for your own apps' },
+ { status: 403 }
+ );
+ }
- const userId = app.appMemberships[0].userId;
+ const result = await registerTemplateReferral(userId, {
+ appId,
+ githubUsername,
+ templateUrl,
+ });
- const result = await registerTemplateReferral(userId, {
- appId,
- githubUsername,
- templateUrl,
- });
+ if (result.status === 'registered') {
+ return NextResponse.json({
+ success: true,
+ status: 'registered',
+ message: `Template creator ${result.referrerUsername} registered as referrer`,
+ referrerUsername: result.referrerUsername,
+ });
+ }
- if (result.status === 'registered') {
- return NextResponse.json({
- success: true,
- status: 'registered',
- message: `Template creator ${result.referrerUsername} registered as referrer`,
- referrerUsername: result.referrerUsername,
- });
- }
+ if (result.status === 'skipped') {
+ return NextResponse.json({
+ success: true,
+ status: 'skipped',
+ message: result.reason || 'Referral skipped',
+ reason: result.reason,
+ });
+ }
- if (result.status === 'skipped') {
return NextResponse.json({
success: true,
- status: 'skipped',
- message: result.reason || 'Referral skipped',
+ status: 'not_found',
+ message: result.reason || 'Template creator not found on Echo',
reason: result.reason,
});
- }
-
- return NextResponse.json({
- success: true,
- status: 'not_found',
- message: result.reason || 'Template creator not found on Echo',
- reason: result.reason,
- });
- } catch (error) {
- const message =
- error instanceof Error ? error.message : 'Unknown error occurred';
+ } catch (error) {
+ const message =
+ error instanceof Error ? error.message : 'Unknown error occurred';
- return NextResponse.json(
- {
- success: false,
- message,
- },
- { status: 400 }
- );
- }
-}
\ No newline at end of file
+ return NextResponse.json(
+ {
+ success: false,
+ message,
+ },
+ { status: 400 }
+ );
+ }
+ });
\ No newline at end of file
Analysis
Unauthenticated API endpoint allows unauthorized referral modification
What fails: /api/v1/referrals/register-template endpoint accepts any appId without authentication, allowing attackers to set themselves as referrers for arbitrary apps
How to reproduce:
# Call endpoint with any app ID and attacker's GitHub username:
curl -X POST https://echo.merit.systems/api/v1/referrals/register-template \
-H "Content-Type: application/json" \
-d '{"appId":"<any-app-id>","githubUsername":"attacker","templateUrl":"https://github.com/attacker/repo"}'Result: Returns 200 OK and sets attacker as referrer for the target app's owner without permission checks. The endpoint looks up the app owner from the database and modifies their referral data directly.
Expected: Should return 401/403 Unauthorized. API endpoints that modify user data require authentication via authRoute middleware (like /api/v1/user/referral/route.ts)
Impact: Authorization bypass allows hijacking referral earnings by setting arbitrary referrers for any app in the system
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yoo @rsproule the authentication concern on this /api/v1/referrals/register-template, I believe this isn't significant because app builders typically won't reveal their app ID publicly before scaffolding with echo-start, and once a referrer is set, subsequent calls are automatically skipped so it can't be overridden by echo start.
|
some problems:
|
I've worked on them.
example of echo.config.json: |
Close #612
When a user provides a template URL (e.g., https://github.com/trynax/template), the system: