Compare commits
1 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
f2a240fdd3 |
|
|
@ -30,12 +30,14 @@ async function comparePasswords(supplied: string, stored: string) {
|
|||
return timingSafeEqual(hashedBuf, suppliedBuf);
|
||||
}
|
||||
|
||||
if (!process.env.SESSION_SECRET) {
|
||||
console.warn("[auth] WARNING: SESSION_SECRET env var not set. Using insecure fallback. Set SESSION_SECRET in production.");
|
||||
}
|
||||
// CORREÇÃO: Session secret seguro - obrigatório em produção
|
||||
const SESSION_SECRET = process.env.SESSION_SECRET ||
|
||||
(process.env.NODE_ENV === 'production'
|
||||
? (() => { throw new Error('SESSION_SECRET obrigatório em produção'); })()
|
||||
: randomBytes(32).toString('hex'));
|
||||
|
||||
const sessionSettings: session.SessionOptions = {
|
||||
secret: process.env.SESSION_SECRET || `arcadia-dev-${Math.random().toString(36)}`,
|
||||
secret: SESSION_SECRET,
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
store: storage.sessionStore,
|
||||
|
|
|
|||
|
|
@ -6,9 +6,26 @@ import ws from "ws";
|
|||
neonConfig.webSocketConstructor = ws;
|
||||
|
||||
const app = express();
|
||||
const PORT = 8006;
|
||||
const PORT = process.env.PORT || 8006;
|
||||
|
||||
app.use(cors());
|
||||
// CORREÇÃO: CORS restrito - whitelist de origens permitidas
|
||||
const allowedOrigins = [
|
||||
'http://localhost:5000',
|
||||
'https://localhost:5000',
|
||||
process.env.FRONTEND_URL,
|
||||
process.env.DOMAIN ? `https://${process.env.DOMAIN}` : null,
|
||||
].filter(Boolean);
|
||||
|
||||
app.use(cors({
|
||||
origin: (origin, callback) => {
|
||||
if (!origin || allowedOrigins.includes(origin)) {
|
||||
callback(null, true);
|
||||
} else {
|
||||
callback(new Error('CORS não permitido'));
|
||||
}
|
||||
},
|
||||
credentials: true
|
||||
}));
|
||||
app.use(express.json());
|
||||
|
||||
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
||||
|
|
|
|||
|
|
@ -182,11 +182,22 @@ export async function registerRoutes(
|
|||
// Arcádia Plus - SSO routes (proxy already registered at top)
|
||||
app.use("/api/plus/sso", plusSsoRoutes);
|
||||
|
||||
// CORREÇÃO: /api/tenants protegido - apenas admin vê todos
|
||||
app.get("/api/tenants", async (req: any, res) => {
|
||||
if (!req.isAuthenticated()) {
|
||||
return res.status(401).json({ error: "Authentication required" });
|
||||
}
|
||||
try {
|
||||
// Se não for admin, retornar apenas o tenant do usuário
|
||||
if (req.user?.role !== 'admin') {
|
||||
if (req.user?.tenantId) {
|
||||
const tenant = await storage.getTenant(req.user.tenantId);
|
||||
return res.json(tenant ? [tenant] : []);
|
||||
}
|
||||
return res.status(403).json({ error: "Access denied" });
|
||||
}
|
||||
|
||||
// Admin vê todos
|
||||
const tenants = await storage.getTenants();
|
||||
res.json(tenants);
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export interface IStorage {
|
|||
sessionStore: session.Store;
|
||||
|
||||
getUser(id: string): Promise<User | undefined>;
|
||||
getUserByUsername(username: string): Promise<User | undefined>;
|
||||
getUserByUsername(username: string, tenantId?: number): Promise<User | undefined>;
|
||||
createUser(user: InsertUser): Promise<User>;
|
||||
getEnrichedUser(user: User): Promise<any>;
|
||||
|
||||
|
|
@ -28,6 +28,7 @@ export interface IStorage {
|
|||
getUserApplications(userId: string): Promise<Application[]>;
|
||||
assignApplicationToUser(userId: string, applicationId: string): Promise<void>;
|
||||
removeApplicationFromUser(userId: string, applicationId: string): Promise<void>;
|
||||
getTenant(id: number): Promise<{ id: number; name: string; slug: string; tenantType?: string; plan?: string; status?: string } | undefined>;
|
||||
getTenants(): Promise<{ id: number; name: string; slug: string }[]>;
|
||||
}
|
||||
|
||||
|
|
@ -46,8 +47,21 @@ export class DatabaseStorage implements IStorage {
|
|||
return user;
|
||||
}
|
||||
|
||||
async getUserByUsername(username: string): Promise<User | undefined> {
|
||||
// CORREÇÃO: getUserByUsername com validação de tenant
|
||||
async getUserByUsername(username: string, tenantId?: number): Promise<User | undefined> {
|
||||
const [user] = await db.select().from(users).where(eq(users.username, username));
|
||||
|
||||
// Se tenantId foi passado, validar que usuário pertence a esse tenant
|
||||
if (tenantId && user) {
|
||||
const [tenantUser] = await db.select()
|
||||
.from(tenantUsers)
|
||||
.where(and(
|
||||
eq(tenantUsers.userId, user.id),
|
||||
eq(tenantUsers.tenantId, tenantId)
|
||||
));
|
||||
if (!tenantUser) return undefined;
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
|
|
@ -146,6 +160,19 @@ export class DatabaseStorage implements IStorage {
|
|||
return enriched;
|
||||
}
|
||||
|
||||
// CORREÇÃO: getTenant para buscar tenant específico
|
||||
async getTenant(id: number): Promise<{ id: number; name: string; slug: string; tenantType?: string; plan?: string; status?: string } | undefined> {
|
||||
const [tenant] = await db.select({
|
||||
id: tenants.id,
|
||||
name: tenants.name,
|
||||
slug: tenants.slug,
|
||||
tenantType: tenants.tenantType,
|
||||
plan: tenants.plan,
|
||||
status: tenants.status
|
||||
}).from(tenants).where(eq(tenants.id, id));
|
||||
return tenant;
|
||||
}
|
||||
|
||||
async getTenants(): Promise<{ id: number; name: string; slug: string }[]> {
|
||||
const result = await db.select({
|
||||
id: tenants.id,
|
||||
|
|
|
|||
Loading…
Reference in New Issue