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:
18
packages/shared-auth/package.json
Normal file
18
packages/shared-auth/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
114
packages/shared-auth/src/config/auth.config.ts
Normal file
114
packages/shared-auth/src/config/auth.config.ts
Normal 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);
|
||||
},
|
||||
},
|
||||
};
|
||||
25
packages/shared-auth/src/index.ts
Normal file
25
packages/shared-auth/src/index.ts
Normal 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';
|
||||
62
packages/shared-auth/src/middleware/auth.middleware.ts
Normal file
62
packages/shared-auth/src/middleware/auth.middleware.ts
Normal 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 });
|
||||
}
|
||||
}
|
||||
81
packages/shared-auth/src/models/auth.models.ts
Normal file
81
packages/shared-auth/src/models/auth.models.ts
Normal 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'),
|
||||
});
|
||||
12
packages/shared-auth/tsconfig.json
Normal file
12
packages/shared-auth/tsconfig.json
Normal 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"]
|
||||
}
|
||||
Reference in New Issue
Block a user