FRE-4529: Transfer ShieldAI code from FrenoCorp repo

Transferred ShieldAI-related files mistakenly placed in ~/code/FrenoCorp:
- Services: spamshield (feature-flags, audit-logger, error-handler), voiceprint (config, service, feature-flags), darkwatch (pipeline, scan, scheduler, watchlist, webhook)
- Packages: shared-analytics, shared-auth, shared-ui, shared-utils (new); shared-billing, jobs supplemented with unique FC files
- Server: alerts (FC version newer), routes (spamshield, darkwatch, voiceprint)
- Config: turbo.json, tsconfig.base.json, vite/vitest configs, drizzle, Dockerfile
- VoicePrint ML service
- Examples

Pending: apps/{api,web,mobile}/ structured merge, shared-db/db mapping

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-05-02 10:13:13 -04:00
parent 8687868632
commit 1e42c4a5c2
45 changed files with 4837 additions and 562 deletions

View File

@@ -0,0 +1,18 @@
{
"name": "@shieldsai/shared-auth",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "src/index.ts",
"types": "src/index.ts",
"scripts": {
"lint": "eslint src/"
},
"dependencies": {
"next-auth": "^4.24.0",
"zod": "^4.3.6"
},
"devDependencies": {
"typescript": "^5.3.3"
}
}

View File

@@ -0,0 +1,114 @@
import { NextAuthOptions } from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import GoogleProvider from 'next-auth/providers/google';
import AppleProvider from 'next-auth/providers/apple';
import { z } from 'zod';
// Environment variables
const envSchema = z.object({
NEXTAUTH_URL: z.string().url(),
NEXTAUTH_SECRET: z.string().min(32),
GOOGLE_CLIENT_ID: z.string(),
GOOGLE_CLIENT_SECRET: z.string(),
APPLE_CLIENT_ID: z.string(),
APPLE_CLIENT_SECRET: z.string(),
DATABASE_URL: z.string().url(),
});
export const authEnv = envSchema.parse({
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET,
APPLE_CLIENT_ID: process.env.APPLE_CLIENT_ID,
APPLE_CLIENT_SECRET: process.env.APPLE_CLIENT_SECRET,
DATABASE_URL: process.env.DATABASE_URL,
});
// Role-based access control
export type UserRole = 'user' | 'family_admin' | 'family_member' | 'support';
export const userRoles: UserRole[] = ['user', 'family_admin', 'family_member', 'support'];
// Family group types
export type FamilyGroup = {
id: string;
name: string;
members: string[]; // user IDs
createdAt: Date;
updatedAt: Date;
};
// NextAuth options
export const authOptions: NextAuthOptions = {
providers: [
CredentialsProvider({
name: 'Credentials',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
throw new Error('Email and password required');
}
// TODO: Validate against database
const user = {
id: '1',
email: credentials.email,
name: credentials.email.split('@')[0],
role: 'user' as UserRole,
};
return user;
},
}),
GoogleProvider({
clientId: authEnv.GOOGLE_CLIENT_ID,
clientSecret: authEnv.GOOGLE_CLIENT_SECRET,
}),
AppleProvider({
clientId: authEnv.APPLE_CLIENT_ID,
clientSecret: authEnv.APPLE_CLIENT_SECRET,
}),
],
session: {
strategy: 'jwt',
maxAge: 30 * 24 * 60 * 60, // 30 days
},
pages: {
signIn: '/auth/signin',
signOut: '/auth/signout',
error: '/auth/error',
},
callbacks: {
async jwt({ token, user, account }) {
if (user) {
token.id = user.id;
token.role = (user as any).role;
}
if (account) {
token.provider = account.provider;
token.accessToken = account.access_token;
}
return token;
},
async session({ session, token }) {
if (session.user) {
session.user.id = token.id as string;
session.user.role = token.role as UserRole;
}
return session;
},
},
events: {
async createUser({ user }) {
// TODO: Create default family group
console.log('New user created:', user.email);
},
},
};

View File

@@ -0,0 +1,25 @@
// Config
export { authOptions, authEnv, userRoles } from './config/auth.config';
export type { UserRole, FamilyGroup } from './config/auth.config';
// Middleware
export { withAuth, withRole, protectApiRoute } from './middleware/auth.middleware';
// Models
export {
userSchema,
familyGroupSchema,
familyMemberSchema,
sessionSchema,
accountSchema,
createUserSchema,
createFamilyGroupSchema,
addFamilyMemberSchema,
} from './models/auth.models';
export type {
User,
FamilyGroup as AuthFamilyGroup,
FamilyMember,
Session,
Account,
} from './models/auth.models';

View File

@@ -0,0 +1,62 @@
import { NextRequest, NextResponse } from 'next-auth/react';
import { UserRole } from '../config/auth.config';
/**
* Middleware to protect routes that require authentication
*/
export function withAuth(
request: NextRequest,
options?: {
signInPath?: string;
}
): NextResponse {
const token = request.cookies.get('next-auth.session-token')?.value;
const signInPath = options?.signInPath ?? '/auth/signin';
if (!token) {
const signInUrl = new URL(signInPath, request.url);
signInUrl.searchParams.set('callbackUrl', request.nextUrl.pathname);
return NextResponse.redirect(signInUrl);
}
return NextResponse.next();
}
/**
* Middleware to check if user has required role
*/
export function withRole(
response: NextResponse,
request: NextRequest,
requiredRoles: UserRole[]
): NextResponse {
const token = request.cookies.get('next-auth.session-token')?.value;
if (!token) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// TODO: Decode JWT and check role
// For now, allow all authenticated users
return response;
}
/**
* Middleware to protect API routes
*/
export function protectApiRoute(request: NextRequest): NextResponse {
const authHeader = request.headers.get('authorization');
if (!authHeader?.startsWith('Bearer ')) {
return NextResponse.json({ error: 'Missing or invalid token' }, { status: 401 });
}
const token = authHeader.split(' ')[1];
try {
// TODO: Verify JWT token
return NextResponse.next();
} catch (error) {
return NextResponse.json({ error: 'Invalid token' }, { status: 401 });
}
}

View File

@@ -0,0 +1,81 @@
import { z } from 'zod';
// User schema
export const userSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string().min(1),
image: z.string().url().optional(),
role: z.enum(['user', 'family_admin', 'family_member', 'support']),
emailVerified: z.date().optional(),
createdAt: z.date(),
updatedAt: z.date(),
});
export type User = z.infer<typeof userSchema>;
// Family group schema
export const familyGroupSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
ownerId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
});
export type FamilyGroup = z.infer<typeof familyGroupSchema>;
// Family member schema
export const familyMemberSchema = z.object({
id: z.string().uuid(),
groupId: z.string().uuid(),
userId: z.string().uuid(),
role: z.enum(['owner', 'admin', 'member']),
joinedAt: z.date(),
});
export type FamilyMember = z.infer<typeof familyMemberSchema>;
// Session schema
export const sessionSchema = z.object({
id: z.string().uuid(),
userId: z.string().uuid(),
sessionToken: z.string(),
expires: z.date(),
createdAt: z.date(),
});
export type Session = z.infer<typeof sessionSchema>;
// Account schema (for OAuth)
export const accountSchema = z.object({
id: z.string().uuid(),
userId: z.string().uuid(),
provider: z.string(),
providerAccountId: z.string(),
access_token: z.string().optional(),
refresh_token: z.string().optional(),
expires_at: z.number().optional(),
token_type: z.string().optional(),
scope: z.string().optional(),
});
export type Account = z.infer<typeof accountSchema>;
// Validation schemas for API
export const createUserSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
name: z.string().min(1),
});
export const createFamilyGroupSchema = z.object({
name: z.string().min(1).max(100),
ownerId: z.string().uuid(),
});
export const addFamilyMemberSchema = z.object({
groupId: z.string().uuid(),
userId: z.string().uuid(),
role: z.enum(['admin', 'member']).default('member'),
});

View File

@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}