feat(saas): full SquareMCP SaaS platform v1
- JWT auth with bcrypt password hashing, cookie sessions, forgot/reset password - Per-user encrypted credential storage (Redis + AES-256-GCM) for all 9 platforms - Usage tracking with monthly limits per plan (free/starter/growth/enterprise) - Invoice generation and retrieval (admin + user views) - Admin panel with customer listing (role-based access) - Web app UI at app.squaremcp.com — login, dashboard, connections, usage, invoices - Unified auth middleware: API key, OAuth Bearer, and JWT cookie support - Facebook Graph API fixes: published_posts endpoint, photo/video post support - TikTok sandbox compliance: SELF_ONLY privacy for unaudited apps - URL verification files for TikTok app review
This commit is contained in:
98
src/auth.ts
Normal file
98
src/auth.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import bcryptjs from 'bcryptjs';
|
||||
const { hash, compare } = bcryptjs;
|
||||
import jwt from 'jsonwebtoken';
|
||||
const { sign, verify } = jwt;
|
||||
import { getPool } from './db.js';
|
||||
import type { RowDataPacket } from 'mysql2';
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET ?? process.env.CREDENTIAL_ENCRYPTION_KEY ?? 'dev-secret-change-me';
|
||||
const SALT_ROUNDS = 12;
|
||||
|
||||
export interface JWTPayload {
|
||||
sub: string; // customer id
|
||||
email: string;
|
||||
plan: string;
|
||||
}
|
||||
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
return hash(password, SALT_ROUNDS);
|
||||
}
|
||||
|
||||
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
|
||||
return compare(password, hash);
|
||||
}
|
||||
|
||||
export function signJWT(payload: JWTPayload): string {
|
||||
return sign(payload, JWT_SECRET, { expiresIn: '7d' });
|
||||
}
|
||||
|
||||
export function verifyJWT(token: string): JWTPayload {
|
||||
return verify(token, JWT_SECRET) as JWTPayload;
|
||||
}
|
||||
|
||||
interface CustomerRow extends RowDataPacket {
|
||||
id: string;
|
||||
email: string;
|
||||
plan: string;
|
||||
active: boolean;
|
||||
api_key: string;
|
||||
password_hash: string | null;
|
||||
}
|
||||
|
||||
export async function findCustomerByEmail(email: string): Promise<CustomerRow | null> {
|
||||
const [rows] = await getPool().query<CustomerRow[]>(
|
||||
'SELECT id, email, plan, active, api_key, password_hash FROM customers WHERE email = ?',
|
||||
[email]
|
||||
);
|
||||
return rows[0] ?? null;
|
||||
}
|
||||
|
||||
export async function findCustomerById(id: string): Promise<CustomerRow | null> {
|
||||
const [rows] = await getPool().query<CustomerRow[]>(
|
||||
'SELECT id, email, plan, active, api_key, password_hash FROM customers WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
return rows[0] ?? null;
|
||||
}
|
||||
|
||||
export async function createCustomer(
|
||||
id: string,
|
||||
email: string,
|
||||
passwordHash: string,
|
||||
apiKey: string
|
||||
): Promise<void> {
|
||||
await getPool().query(
|
||||
'INSERT INTO customers (id, email, password_hash, api_key, plan, active) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
[id, email, passwordHash, apiKey, 'free', true]
|
||||
);
|
||||
}
|
||||
|
||||
export async function setResetToken(email: string, token: string): Promise<boolean> {
|
||||
const [result] = await getPool().query<any>(
|
||||
'UPDATE customers SET reset_token = ?, reset_expires_at = DATE_ADD(NOW(), INTERVAL 1 HOUR) WHERE email = ?',
|
||||
[token, email]
|
||||
);
|
||||
return result.affectedRows > 0;
|
||||
}
|
||||
|
||||
export async function findCustomerByResetToken(token: string) {
|
||||
const [rows] = await getPool().query<CustomerRow[]>(
|
||||
'SELECT id, email, plan, active, api_key, password_hash FROM customers WHERE reset_token = ? AND reset_expires_at > NOW()',
|
||||
[token]
|
||||
);
|
||||
return rows[0] ?? null;
|
||||
}
|
||||
|
||||
export async function clearResetToken(customerId: string): Promise<void> {
|
||||
await getPool().query(
|
||||
'UPDATE customers SET reset_token = NULL, reset_expires_at = NULL WHERE id = ?',
|
||||
[customerId]
|
||||
);
|
||||
}
|
||||
|
||||
export async function updatePassword(customerId: string, passwordHash: string): Promise<void> {
|
||||
await getPool().query(
|
||||
'UPDATE customers SET password_hash = ? WHERE id = ?',
|
||||
[passwordHash, customerId]
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user