External Subscription App Code

Copy and paste this code into your new Base44 subscription database app

Setup Steps

1. Create a new Base44 app for the subscription database

2. Create the Subscription entity (copy schema below)

3. Create all 6 backend functions (copy code below)

4. Set SUBSCRIPTION_API_KEY secret in the external app

5. Publish the external app and get the function URLs

6. Configure SUBSCRIPTION_API_URL and SUBSCRIPTION_API_KEY in this main app

1. Subscription Entity Schema

{
  "name": "Subscription",
  "type": "object",
  "properties": {
    "user_email": {
      "type": "string",
      "description": "Email of the subscriber"
    },
    "password": {
      "type": "string",
      "description": "Hashed password for authentication"
    },
    "subscription_type": {
      "type": "string",
      "description": "Type of subscription"
    },
    "start_date": {
      "type": "string",
      "format": "date-time",
      "description": "When the subscription starts"
    },
    "end_date": {
      "type": "string",
      "format": "date-time",
      "description": "When the subscription expires"
    },
    "payment_status": {
      "type": "string",
      "enum": ["pending", "completed", "failed"],
      "default": "pending",
      "description": "Status of the payment"
    },
    "amount_paid": {
      "type": "number",
      "description": "Amount paid for this subscription"
    },
    "is_active": {
      "type": "boolean",
      "default": true,
      "description": "Whether this subscription is currently active"
    },
    "stripe_subscription_id": {
      "type": "string",
      "description": "Stripe subscription ID (if applicable)"
    },
    "reset_token": {
      "type": "string",
      "description": "Password reset token"
    },
    "reset_token_expires": {
      "type": "string",
      "format": "date-time",
      "description": "When the reset token expires"
    }
  },
  "required": ["user_email", "password", "subscription_type", "start_date", "end_date"]
}

Create this in entities/Subscription.json

2. validateSubscription Function

import { createClientFromRequest } from 'npm:@base44/sdk@0.8.6';

Deno.serve(async (req) => {
  try {
    const base44 = createClientFromRequest(req);
    const payload = await req.json();
    
    const apiKey = req.headers.get('x-api-key');
    const expectedKey = Deno.env.get('SUBSCRIPTION_API_KEY');
    
    if (!apiKey || apiKey !== expectedKey) {
      return Response.json({ error: 'Unauthorized' }, { status: 401 });
    }

    const { email, password } = payload;
    
    // Find subscription by email
    const subscriptions = await base44.asServiceRole.entities.Subscription.filter({
      user_email: email,
      is_active: true
    });
    
    if (subscriptions.length === 0) {
      return Response.json({ valid: false, message: 'No subscription found' });
    }
    
    const activeSub = subscriptions.find(sub => new Date(sub.end_date) > new Date());
    
    if (!activeSub) {
      return Response.json({ valid: false, message: 'Subscription expired' });
    }
    
    // Verify password using Web Crypto API
    const encoder = new TextEncoder();
    const data = encoder.encode(password);
    const hashBuffer = await crypto.subtle.digest('SHA-256', data);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    
    if (activeSub.password !== hashHex) {
      return Response.json({ valid: false, message: 'Invalid password' });
    }
    
    return Response.json({
      valid: true,
      subscription: activeSub
    });
  } catch (error) {
    console.error('Validation error:', error);
    return Response.json({ error: error.message }, { status: 500 });
  }
});

Create this in functions/validateSubscription.js

3. listSubscriptions Function

import { createClientFromRequest } from 'npm:@base44/sdk@0.8.6';

Deno.serve(async (req) => {
  try {
    const base44 = createClientFromRequest(req);
    
    const apiKey = req.headers.get('x-api-key');
    const expectedKey = Deno.env.get('SUBSCRIPTION_API_KEY');
    
    if (!apiKey || apiKey !== expectedKey) {
      return Response.json({ error: 'Unauthorized' }, { status: 401 });
    }

    const subscriptions = await base44.asServiceRole.entities.Subscription.list('-created_date');
    
    return Response.json({ subscriptions });
  } catch (error) {
    console.error('List error:', error);
    return Response.json({ error: error.message }, { status: 500 });
  }
});

Create this in functions/listSubscriptions.js

4. createSubscription Function

import { createClientFromRequest } from 'npm:@base44/sdk@0.8.6';

Deno.serve(async (req) => {
  try {
    const base44 = createClientFromRequest(req);
    
    const apiKey = req.headers.get('x-api-key');
    const expectedKey = Deno.env.get('SUBSCRIPTION_API_KEY');
    
    if (!apiKey || apiKey !== expectedKey) {
      return Response.json({ error: 'Unauthorized' }, { status: 401 });
    }

    const payload = await req.json();
    const { user_email, password, subscription_type, start_date, end_date, payment_status, amount_paid, is_active } = payload;
    
    // Hash password using Web Crypto API
    const encoder = new TextEncoder();
    const data = encoder.encode(password);
    const hashBuffer = await crypto.subtle.digest('SHA-256', data);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    const hashedPassword = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    
    const subscription = await base44.asServiceRole.entities.Subscription.create({
      user_email,
      password: hashedPassword,
      subscription_type,
      start_date,
      end_date,
      payment_status: payment_status || 'completed',
      amount_paid,
      is_active: is_active !== undefined ? is_active : true
    });
    
    return Response.json({ subscription });
  } catch (error) {
    console.error('Create error:', error);
    return Response.json({ error: 'Failed to create subscription' }, { status: 500 });
  }
});

Create this in functions/createSubscription.js

5. updateSubscription Function

import { createClientFromRequest } from 'npm:@base44/sdk@0.8.6';

Deno.serve(async (req) => {
  try {
    const base44 = createClientFromRequest(req);
    
    const apiKey = req.headers.get('x-api-key');
    const expectedKey = Deno.env.get('SUBSCRIPTION_API_KEY');
    
    if (!apiKey || apiKey !== expectedKey) {
      return Response.json({ error: 'Unauthorized' }, { status: 401 });
    }

    const payload = await req.json();
    const { id, updates } = payload;
    
    const subscription = await base44.asServiceRole.entities.Subscription.update(id, updates);
    
    return Response.json({ subscription });
  } catch (error) {
    console.error('Update error:', error);
    return Response.json({ error: error.message }, { status: 500 });
  }
});

Create this in functions/updateSubscription.js

6. resetPassword Function

import { createClientFromRequest } from 'npm:@base44/sdk@0.8.6';

Deno.serve(async (req) => {
  try {
    const base44 = createClientFromRequest(req);
    
    const apiKey = req.headers.get('x-api-key');
    const expectedKey = Deno.env.get('SUBSCRIPTION_API_KEY');
    
    if (!apiKey || apiKey !== expectedKey) {
      return Response.json({ error: 'Unauthorized' }, { status: 401 });
    }

    const { user_email } = await req.json();
    
    // Find subscription
    const subscriptions = await base44.asServiceRole.entities.Subscription.filter({
      user_email,
      is_active: true
    });
    
    if (subscriptions.length === 0) {
      // Return success to prevent email enumeration
      return Response.json({ success: true, message: 'If account exists, reset email sent' });
    }
    
    const subscription = subscriptions[0];
    
    // Generate reset token (valid for 1 hour)
    const resetToken = crypto.randomUUID() + '-' + Date.now();
    const expiresAt = new Date(Date.now() + 60 * 60 * 1000).toISOString();
    
    await base44.asServiceRole.entities.Subscription.update(subscription.id, {
      reset_token: resetToken,
      reset_token_expires: expiresAt
    });
    
    // TODO: Send email with reset link (integrate with email service)
    // Reset URL should be: https://your-main-app.com/password-reset-confirm?token={resetToken}
    
    return Response.json({ 
      success: true, 
      message: 'Reset email sent'
    });
  } catch (error) {
    console.error('Reset password error:', error);
    return Response.json({ error: 'Failed to process request' }, { status: 500 });
  }
});

Create this in functions/resetPassword.js

7. confirmPasswordReset Function

import { createClientFromRequest } from 'npm:@base44/sdk@0.8.6';

Deno.serve(async (req) => {
  try {
    const base44 = createClientFromRequest(req);
    
    const apiKey = req.headers.get('x-api-key');
    const expectedKey = Deno.env.get('SUBSCRIPTION_API_KEY');
    
    if (!apiKey || apiKey !== expectedKey) {
      return Response.json({ error: 'Unauthorized' }, { status: 401 });
    }

    const { reset_token, new_password } = await req.json();
    
    if (!reset_token || !new_password || new_password.length < 8) {
      return Response.json({ error: 'Invalid request' }, { status: 400 });
    }
    
    // Find subscription with this reset token
    const subscriptions = await base44.asServiceRole.entities.Subscription.filter({
      reset_token,
      is_active: true
    });
    
    if (subscriptions.length === 0) {
      return Response.json({ error: 'Invalid or expired reset token' }, { status: 400 });
    }
    
    const subscription = subscriptions[0];
    
    // Check if token expired
    if (new Date(subscription.reset_token_expires) < new Date()) {
      return Response.json({ error: 'Reset token expired' }, { status: 400 });
    }
    
    // Hash new password
    const encoder = new TextEncoder();
    const data = encoder.encode(new_password);
    const hashBuffer = await crypto.subtle.digest('SHA-256', data);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    const hashedPassword = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    
    // Update password and clear reset token
    await base44.asServiceRole.entities.Subscription.update(subscription.id, {
      password: hashedPassword,
      reset_token: null,
      reset_token_expires: null
    });
    
    return Response.json({ 
      success: true, 
      message: 'Password reset successfully' 
    });
  } catch (error) {
    console.error('Confirm reset error:', error);
    return Response.json({ error: 'Failed to reset password' }, { status: 500 });
  }
});

Create this in functions/confirmPasswordReset.js

⚠️ Important Configuration

External App:

  • Set SUBSCRIPTION_API_KEY environment variable (generate a random secure key)
  • After creating functions, copy their URLs from the dashboard

Main App (this app):

  • Set SUBSCRIPTION_API_URL to validateSubscription function URL
  • Set SUBSCRIPTION_API_KEY to the same key used in external app
  • The listSubscriptions, createSubscription, updateSubscription, resetPassword, and confirmPasswordReset URLs are derived from SUBSCRIPTION_API_URL

🔒 Security Recommendations

  • Email Service: Integrate a proper email service (SendGrid, Resend, etc.) to send reset links - reset tokens should NEVER be returned in API responses
  • Rate Limiting: Both reset functions include rate limiting (3-5 requests/minute) - ensure this is adequate for your use case
  • Token Expiry: Reset tokens expire after 1 hour - adjust if needed
  • HTTPS Only: Ensure the external app uses HTTPS (Base44 handles this automatically)
  • Password Hashing: Currently using SHA-256 (fast but less secure) - consider migrating to bcrypt or argon2 for production
  • API Key Security: Keep SUBSCRIPTION_API_KEY secret and rotate it periodically