@syngrisi/syngrisi
Version:
Syngrisi - Visual Testing Tool
1,286 lines (1,262 loc) • 34.4 kB
JavaScript
// src/server/utils/isJSON.ts
var isJSON = (text) => {
if (!text) return false;
const isValid = /^[\],:{}\s]*$/.test(
text.replace(/\\["\\\/bfnrtu]/g, "@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]").replace(/(?:^|:|,)(?:\s*\[)+/g, "")
);
return isValid;
};
var isJSON_default = isJSON;
// src/server/utils/catchAsync.ts
var catchAsync = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch((err) => {
return next(err);
});
};
var catchAsync_default = catchAsync;
// src/server/utils/deserializeIfJSON.ts
import mongoose from "mongoose";
var { EJSON } = mongoose.mongo.BSON;
var EXTENDED_JSON_KEYS = [
"$oid",
"$date",
"$numberInt",
"$numberLong",
"$numberDouble",
"$numberDecimal",
"$regularExpression",
"$binary",
"$timestamp"
];
var containsExtendedJsonMarkers = (text) => EXTENDED_JSON_KEYS.some((marker) => text.includes(`"${marker}"`));
var deserializeIfJSON = (text) => {
if (isJSON_default(text)) {
if (containsExtendedJsonMarkers(text)) {
return EJSON.parse(text) || void 0;
}
return JSON.parse(text) || void 0;
}
return text;
};
var deserializeIfJSON_default = deserializeIfJSON;
// src/server/models/Check.model.ts
import mongoose2, { Schema } from "mongoose";
// src/server/models/plugins/paginate.plugin.ts
var paginate = (schema) => {
schema.statics.paginate = async function(filter, options) {
let sort;
if (options.sortBy) {
const sortingCriteria = [];
let primaryOrder = "desc";
options.sortBy.split(",").forEach((sortOption, index) => {
const [key, order] = sortOption.split(":");
if (index === 0) primaryOrder = order || "asc";
sortingCriteria.push((order === "desc" ? "-" : "") + key);
});
if (!sortingCriteria.some((s) => s === "_id" || s === "-_id")) {
sortingCriteria.push((primaryOrder === "desc" ? "-" : "") + "_id");
}
sort = sortingCriteria.join(" ");
} else {
sort = { _id: -1 };
}
const limit = options.limit && parseInt(options.limit.toString(), 10) >= 0 ? parseInt(options.limit.toString(), 10) : 10;
const page = options.page && parseInt(options.page.toString(), 10) > 0 ? parseInt(options.page.toString(), 10) : 1;
const skip = (page - 1) * limit;
const countStrategy = options.countStrategy ?? (filter && Object.keys(filter).length === 0 ? "estimated" : "exact");
const countPromise = countStrategy === "estimated" ? this.estimatedDocumentCount().exec() : this.countDocuments(filter).exec();
let docsPromise = this.find(filter).sort(sort).skip(skip).limit(limit);
if (options.populate) {
options.populate.split(",").forEach((populateOption) => {
docsPromise = docsPromise.populate(
populateOption.split(".").reverse().reduce((a, b) => ({ path: b, populate: a }))
);
});
}
docsPromise = docsPromise.exec();
return Promise.all([countPromise, docsPromise]).then((values) => {
const [totalResults, results] = values;
const totalPages = Math.ceil(totalResults / limit);
const result = {
results,
page,
limit,
totalPages,
totalResults,
timestamp: Number(Date.now() + String(process.hrtime()[1]).slice(3, 6))
};
return Promise.resolve(result);
});
};
};
var paginate_plugin_default = paginate;
// src/server/models/plugins/toJSON.plugin.ts
var deleteAtPath = (obj, path2, index) => {
if (index === path2.length - 1) {
delete obj[path2[index]];
return;
}
deleteAtPath(obj[path2[index]], path2, index + 1);
};
var toJSON = (schema) => {
let transform;
if (schema.options.toJSON && schema.options.toJSON.transform) {
transform = schema.options.toJSON.transform;
}
schema.options.toJSON = Object.assign(schema.options.toJSON || {}, {
transform(doc, ret, options) {
Object.keys(schema.paths).forEach((path2) => {
if (schema.paths[path2].options && schema.paths[path2].options.private) {
deleteAtPath(ret, path2.split("."), 0);
}
});
ret.id = ret._id.toString();
delete ret.__v;
delete ret.createdAt;
delete ret.updatedAt;
if (transform) {
return transform(doc, ret, options);
}
}
});
};
var toJSON_plugin_default = toJSON;
// src/server/models/Check.model.ts
var CheckSchema = new Schema({
name: {
type: String,
required: [true, 'CheckSchema: The "name" field must be required']
},
test: {
type: Schema.Types.ObjectId,
ref: "VRSTest",
required: [true, 'CheckSchema: The "test" field must be required']
},
suite: {
type: Schema.Types.ObjectId,
ref: "VRSSuite",
required: [true, 'CheckSchema: The "suite" field must be required']
},
app: {
type: Schema.Types.ObjectId,
ref: "VRSApp",
required: [true, 'CheckSchema: The "app" field must be required']
},
branch: {
type: String
},
realBaselineId: {
type: Schema.Types.ObjectId,
ref: "VRSBaseline"
},
baselineId: {
type: Schema.Types.ObjectId,
ref: "VRSSnapshot"
},
actualSnapshotId: {
type: Schema.Types.ObjectId,
ref: "VRSSnapshot"
},
diffId: {
type: Schema.Types.ObjectId,
ref: "VRSSnapshot"
},
createdDate: {
type: Date,
required: true,
default: Date.now
},
updatedDate: {
type: Date
},
status: {
type: [{
type: String,
enum: {
values: ["new", "pending", "approved", "running", "passed", "failed", "aborted"],
message: "status is required"
}
}],
default: ["new"]
},
browserName: {
type: String
},
browserVersion: {
type: String
},
browserFullVersion: {
type: String
},
viewport: {
type: String
},
os: {
type: String
},
domDump: {
type: String
},
result: {
type: String,
default: "{}"
},
run: {
type: Schema.Types.ObjectId
},
markedAs: {
type: String,
enum: ["bug", "accepted"]
},
markedDate: {
type: Date
},
markedById: {
type: Schema.Types.ObjectId,
ref: "VRSUser"
},
markedByUsername: {
type: String
},
markedBugComment: {
type: String
},
creatorId: {
type: Schema.Types.ObjectId,
ref: "VRSUser"
},
creatorUsername: {
type: String
},
failReasons: {
type: [String]
},
vOffset: {
type: String
},
topStablePixels: {
type: String
},
toleranceThreshold: {
type: Number,
min: 0,
max: 100
},
meta: {
type: Object
}
});
CheckSchema.plugin(toJSON_plugin_default);
CheckSchema.plugin(paginate_plugin_default);
var Check = mongoose2.model("VRSCheck", CheckSchema);
// src/server/models/Log.model.ts
import mongoose3, { Schema as Schema2 } from "mongoose";
var LogSchema = new Schema2({
timestamp: {
type: Date
},
level: {
type: String
},
message: {
type: String
},
meta: {
type: Object
},
hostname: {
type: Object
}
});
LogSchema.plugin(toJSON_plugin_default);
LogSchema.plugin(paginate_plugin_default);
var Log = mongoose3.model("VRSLog", LogSchema);
// src/server/models/App.model.ts
import mongoose4, { Schema as Schema3 } from "mongoose";
var AppSchema = new Schema3({
name: {
type: String,
default: "Others",
unique: true,
required: [true, 'AppSchema: The "name" field must be required']
},
description: {
type: String
},
version: {
type: String
},
updatedDate: {
type: Date
},
createdDate: {
type: Date
},
meta: {
type: Object
}
});
AppSchema.plugin(paginate_plugin_default);
AppSchema.plugin(toJSON_plugin_default);
var App = mongoose4.model("VRSApp", AppSchema);
// src/server/models/Snapshot.model.ts
import mongoose5, { Schema as Schema4 } from "mongoose";
var SnapshotSchema = new Schema4({
name: {
type: String,
required: [true, 'SnapshotSchema: The "name" field must be required']
},
path: {
type: String
},
filename: {
type: String
},
imghash: {
type: String,
required: [true, 'SnapshotSchema: The "imghash" field must be required']
},
createdDate: {
type: Date,
default: Date.now
},
vOffset: {
type: Number
},
hOffset: {
type: Number
}
});
SnapshotSchema.plugin(toJSON_plugin_default);
SnapshotSchema.plugin(paginate_plugin_default);
var Snapshot = mongoose5.model("VRSSnapshot", SnapshotSchema);
// src/server/models/AppSettings.model.ts
import mongoose6, { Schema as Schema5 } from "mongoose";
var AppSettingsSchema = new Schema5({
name: {
type: String,
unique: true,
required: [true, 'AppSettingsSchema: The "name" field must be required']
},
label: {
type: String,
required: [true, 'AppSettingsSchema: The "label" field must be required']
},
description: {
type: String
},
type: {
type: String,
required: [true, 'AppSettingsSchema: The "type" field must be required']
},
value: {
type: Schema5.Types.Mixed,
required: [true, 'AppSettingsSchema: The "value" field must be required']
},
env_variable: {
type: String
},
enabled: {
type: Boolean
}
});
AppSettingsSchema.plugin(toJSON_plugin_default);
var AppSettings = mongoose6.model("VRSAppSettings", AppSettingsSchema);
var AppSettings_model_default = AppSettings;
// src/server/models/Suite.model.ts
import mongoose7, { Schema as Schema6 } from "mongoose";
var SuiteSchema = new Schema6({
name: {
type: String,
default: "Others",
unique: true,
required: [true, 'SuiteSchema: The "name" field must be required']
},
tags: {
type: [String]
},
app: {
type: Schema6.Types.ObjectId,
ref: "VRSApp",
required: [true, 'SuiteSchema: The "app" field must be required']
},
description: {
type: String
},
updatedDate: {
type: Date,
default: Date.now
},
createdDate: {
type: Date
},
meta: {
type: Object
}
});
SuiteSchema.plugin(paginate_plugin_default);
SuiteSchema.plugin(toJSON_plugin_default);
var Suite = mongoose7.model("VRSSuite", SuiteSchema);
// src/server/models/Run.model.ts
import mongoose8, { Schema as Schema7 } from "mongoose";
var RunSchema = new Schema7({
name: {
type: String,
required: [true, 'RunSchema: The "name" field must be required']
},
app: {
type: Schema7.Types.ObjectId,
ref: "VRSApp",
required: [true, 'RunSchema: The "app" field must be required']
},
ident: {
type: String,
unique: true,
required: [true, 'RunSchema: The "ident" field must be required']
},
description: {
type: String
},
updatedDate: {
type: Date,
default: Date.now
},
createdDate: {
type: Date
},
parameters: {
type: [String]
},
meta: {
type: Object
}
});
RunSchema.plugin(paginate_plugin_default);
RunSchema.plugin(toJSON_plugin_default);
var Run = mongoose8.model("VRSRun", RunSchema);
// src/server/models/User.model.ts
import mongoose9, { Schema as Schema8 } from "mongoose";
import plm from "passport-local-mongoose";
var passportLocalMongoose = plm.default || plm;
var UserSchema = new Schema8({
username: {
type: String,
unique: true,
required: [true, 'UserSchema: The "username" field must be required']
},
firstName: {
type: String,
required: [true, 'UserSchema: The "firstName" field must be required']
},
lastName: {
type: String,
required: [true, 'UserSchema: The "lastName" field must be required']
},
role: {
type: String,
enum: ["admin", "reviewer", "user"],
required: [true, 'UserSchema: The "role" field must be required']
},
provider: {
type: String,
default: "local"
},
providerId: {
type: String
},
password: {
type: String
},
token: {
type: String
},
apiKey: {
type: String
},
authSource: {
type: String,
enum: ["local", "jwt", "ldap", "api"],
default: "local"
},
createdDate: {
type: Date
},
updatedDate: {
type: Date
},
expiration: {
type: Date
},
meta: {
type: Object
}
});
UserSchema.statics.isEmailTaken = async function(username, excludeUserId) {
const user = await this.findOne({ username, _id: { $ne: excludeUserId } });
return !!user;
};
UserSchema.plugin(toJSON_plugin_default);
UserSchema.plugin(paginate_plugin_default);
UserSchema.plugin(passportLocalMongoose, { hashField: "password" });
var User = mongoose9.model("VRSUser", UserSchema);
// src/server/models/Baseline.model.ts
import mongoose10, { Schema as Schema9 } from "mongoose";
var BaselineSchema = new Schema9({
snapshootId: {
type: Schema9.Types.ObjectId,
ref: "VRSSnapshot"
},
name: {
type: String,
required: [true, 'VRSBaselineSchema: The "name" field must be required']
},
app: {
type: Schema9.Types.ObjectId,
ref: "VRSApp",
required: [true, 'VRSBaselineSchema: The "app" field must be required']
},
branch: {
type: String
},
browserName: {
type: String
},
browserVersion: {
type: String
},
browserFullVersion: {
type: String
},
viewport: {
type: String
},
os: {
type: String
},
markedAs: {
type: String,
enum: ["bug", "accepted"]
},
lastMarkedDate: {
type: Date
},
createdDate: {
type: Date
},
updatedDate: {
type: Date
},
markedById: {
type: Schema9.Types.ObjectId,
ref: "VRSUser"
},
markedByUsername: {
type: String
},
ignoreRegions: {
type: String
},
boundRegions: {
type: String
},
matchType: {
type: String,
enum: ["antialiasing", "nothing", "colors"]
},
toleranceThreshold: {
type: Number,
default: 0,
min: 0,
max: 100
},
meta: {
type: Object
}
});
BaselineSchema.set("autoIndex", false);
BaselineSchema.plugin(toJSON_plugin_default);
BaselineSchema.plugin(paginate_plugin_default);
BaselineSchema.index({
name: 1,
app: 1,
branch: 1,
browserName: 1,
viewport: 1,
os: 1,
snapshootId: 1
}, { unique: true, name: "baseline_ident_snapshot_idx" });
var Baseline = mongoose10.model("VRSBaseline", BaselineSchema);
// src/server/models/Test.model.ts
import mongoose11, { Schema as Schema10 } from "mongoose";
var TestSchema = new Schema10(
{
name: {
type: String,
required: "TestSchema: the test name is empty"
},
description: {
type: String
},
status: {
type: String
},
browserName: {
type: String
},
browserVersion: {
type: String
},
branch: {
type: String
},
tags: {
type: [String]
},
viewport: {
type: String
},
calculatedViewport: {
type: String
},
os: {
type: String
},
app: {
type: Schema10.Types.ObjectId,
ref: "VRSApp",
required: [true, 'TestSchema: The "app" field must be required']
},
blinking: {
type: Number,
default: 0
},
updatedDate: {
type: Date
},
startDate: {
type: Date
},
checks: [
{
type: mongoose11.Schema.Types.ObjectId,
ref: "VRSCheck"
}
],
suite: {
type: Schema10.Types.ObjectId,
ref: "VRSSuite"
},
run: {
type: Schema10.Types.ObjectId,
ref: "VRSRun"
},
markedAs: {
type: String,
enum: ["Bug", "Accepted", "Unaccepted", "Partially"]
},
creatorId: {
type: Schema10.Types.ObjectId,
ref: "VRSUser"
},
creatorUsername: {
type: String
},
meta: {
type: Object
}
},
{ strictQuery: true }
);
TestSchema.plugin(toJSON_plugin_default);
TestSchema.plugin(paginate_plugin_default);
TestSchema.statics.paginateDistinct = async function(filter, options) {
let sort = { _id: -1 };
if (options.sortBy) {
sort = {};
options.sortBy.split(",").forEach((sortOption) => {
const [key, order] = sortOption.split(":");
sort[key] = order === "desc" ? -1 : 1;
});
}
let limit = options.limit && parseInt(options.limit.toString(), 10) >= 0 ? parseInt(options.limit.toString(), 10) : 10;
limit = limit === 0 ? Number.MAX_SAFE_INTEGER : limit;
const page = options.page && parseInt(options.page.toString(), 10) > 0 ? parseInt(options.page.toString(), 10) : 1;
const skip = (page - 1) * limit;
const groupAggregateObj = { $group: { _id: `$${options.field}` } };
const parsedFilter = typeof filter?.filter === "string" ? deserializeIfJSON_default(filter.filter) || {} : {};
const documentsCount = (await this.aggregate([{ $match: parsedFilter }, groupAggregateObj]).exec()).length;
const aggregatedDocs = (await this.aggregate([
{ $match: parsedFilter },
groupAggregateObj,
{ $sort: sort },
{ $skip: skip },
{ $limit: limit }
])).filter((x) => x._id).map((x) => {
const fieldValue = options.field ? x[options.field] : void 0;
if (Array.isArray(fieldValue) && fieldValue.length > 0) {
return fieldValue[0];
}
return { name: x._id };
});
const totalPages = Math.ceil(documentsCount / limit);
return {
results: aggregatedDocs,
page,
limit,
totalPages,
totalResults: documentsCount,
timestamp: Date.now()
};
};
var Test = mongoose11.model("VRSTest", TestSchema);
// src/server/models/Webhook.model.ts
import mongoose12, { Schema as Schema11 } from "mongoose";
var WebhookSchema = new Schema11({
url: {
type: String,
required: [true, 'WebhookSchema: The "url" field must be required']
},
events: {
type: [String],
default: ["check.updated", "check.created"]
},
secret: {
type: String
},
createdDate: {
type: Date,
default: Date.now
},
meta: {
type: Object
}
});
WebhookSchema.plugin(paginate_plugin_default);
WebhookSchema.plugin(toJSON_plugin_default);
var Webhook = mongoose12.model("VRSWebhook", WebhookSchema);
// src/server/models/ShareToken.model.ts
import mongoose13, { Schema as Schema12 } from "mongoose";
var ShareTokenSchema = new Schema12({
checkId: {
type: Schema12.Types.ObjectId,
ref: "VRSCheck",
required: [true, 'ShareTokenSchema: The "checkId" field is required'],
index: true
},
token: {
type: String,
required: [true, 'ShareTokenSchema: The "token" field is required'],
unique: true,
index: true
},
createdById: {
type: Schema12.Types.ObjectId,
ref: "VRSUser",
required: [true, 'ShareTokenSchema: The "createdById" field is required']
},
createdByUsername: {
type: String,
required: [true, 'ShareTokenSchema: The "createdByUsername" field is required']
},
createdDate: {
type: Date,
required: true,
default: Date.now
},
isRevoked: {
type: Boolean,
default: false
},
revokedDate: {
type: Date
},
revokedById: {
type: Schema12.Types.ObjectId,
ref: "VRSUser"
},
revokedByUsername: {
type: String
}
});
ShareTokenSchema.plugin(toJSON_plugin_default);
ShareTokenSchema.plugin(paginate_plugin_default);
var ShareToken = mongoose13.model("VRSShareToken", ShareTokenSchema);
// src/server/models/DomSnapshot.model.ts
import mongoose14, { Schema as Schema13 } from "mongoose";
var DomSnapshotSchema = new Schema13({
checkId: {
type: Schema13.Types.ObjectId,
ref: "VRSCheck",
required: [true, 'DomSnapshotSchema: The "checkId" field is required'],
index: true
},
baselineId: {
type: Schema13.Types.ObjectId,
ref: "VRSBaseline",
index: true
},
type: {
type: String,
enum: ["actual", "baseline"],
required: [true, 'DomSnapshotSchema: The "type" field is required']
},
filename: {
type: String,
required: [true, 'DomSnapshotSchema: The "filename" field is required']
},
hash: {
type: String,
required: [true, 'DomSnapshotSchema: The "hash" field is required'],
index: true
},
compressed: {
type: Boolean,
default: false
},
originalSize: {
type: Number,
required: [true, 'DomSnapshotSchema: The "originalSize" field is required']
},
compressedSize: {
type: Number
},
createdDate: {
type: Date,
default: Date.now
}
});
DomSnapshotSchema.index({ checkId: 1, type: 1 });
DomSnapshotSchema.index({ baselineId: 1, type: 1 });
DomSnapshotSchema.plugin(toJSON_plugin_default);
DomSnapshotSchema.plugin(paginate_plugin_default);
var DomSnapshot = mongoose14.model("VRSDomSnapshot", DomSnapshotSchema);
// src/server/models/PluginSettings.model.ts
import mongoose15, { Schema as Schema14 } from "mongoose";
var PluginSettingSchemaDefinition = new Schema14({
key: { type: String, required: true },
label: { type: String, required: true },
description: { type: String },
type: {
type: String,
enum: ["string", "number", "boolean", "select", "password"],
required: true
},
defaultValue: { type: Schema14.Types.Mixed },
envVariable: { type: String },
options: [{ value: String, label: String }],
required: { type: Boolean, default: false }
}, { _id: false });
var PluginSettingsSchema = new Schema14({
pluginName: {
type: String,
unique: true,
required: [true, "PluginSettings: pluginName is required"],
index: true
},
displayName: {
type: String,
required: [true, "PluginSettings: displayName is required"]
},
description: {
type: String
},
enabled: {
type: Boolean,
default: false
},
settings: {
type: Schema14.Types.Mixed,
default: {}
},
settingsSchema: {
type: [PluginSettingSchemaDefinition],
default: []
}
}, {
timestamps: true
});
PluginSettingsSchema.plugin(toJSON_plugin_default);
PluginSettingsSchema.statics.getEffectiveConfig = async function(pluginName, envPrefix = "SYNGRISI_PLUGIN_") {
const doc = await this.findOne({ pluginName });
const envPluginKey = pluginName.toUpperCase().replace(/-/g, "_");
const envEnabledKey = `${envPrefix}${envPluginKey}_ENABLED`;
const envEnabled = process.env[envEnabledKey]?.toLowerCase() === "true";
const dbEnabled = doc?.enabled;
const enabled = doc ? dbEnabled : envEnabled;
const config = {};
const schema = doc?.settingsSchema || [];
if (schema.length === 0 && doc?.settings && Object.keys(doc.settings).length > 0) {
for (const [key, value] of Object.entries(doc.settings)) {
config[key] = { value, source: "db" };
}
return { config, enabled };
}
for (const field of schema) {
const envKey = field.envVariable || `${envPrefix}${envPluginKey}_${field.key.toUpperCase()}`;
const envValue = process.env[envKey];
const dbValue = doc?.settings?.[field.key];
if (dbValue !== void 0) {
config[field.key] = { value: dbValue, source: "db" };
} else if (envValue !== void 0) {
let parsedValue = envValue;
if (field.type === "boolean") {
parsedValue = envValue.toLowerCase() === "true";
} else if (field.type === "number") {
parsedValue = parseFloat(envValue);
}
config[field.key] = { value: parsedValue, source: "env" };
} else if (field.defaultValue !== void 0) {
config[field.key] = { value: field.defaultValue, source: "default" };
}
}
return { config, enabled };
};
PluginSettingsSchema.statics.upsertSettings = async function(pluginName, updates) {
return this.findOneAndUpdate(
{ pluginName },
{ $set: updates },
{ upsert: true, new: true, runValidators: true }
);
};
var PluginSettings = mongoose15.model(
"VRSPluginSettings",
PluginSettingsSchema
);
// src/server/utils/hash.ts
import { createHash, randomUUID } from "crypto";
// src/seeds/initialAppSettings.json
var initialAppSettings_default = [
{
name: "first_run",
label: "First Run",
description: "Indicates if the application is running the first time",
type: "Boolean",
value: "true",
enabled: true
},
{
name: "authentication",
label: "Authentication",
description: "Enable application authentication",
type: "Boolean",
value: "false",
enabled: true
},
{
name: "auto_remove_old_checks",
label: "Auto remove old checks",
description: "Automatically delete checks older than the configured number of days once per day",
type: "AutoOldChecks",
value: {
days: 365,
lastRunAt: null
},
enabled: false
},
{
name: "auto_remove_old_logs",
label: "Auto remove old logs",
description: "Automatically delete logs older than the configured number of days once per day",
type: "AutoOldLogs",
value: {
days: 120,
lastRunAt: null
},
enabled: true
},
{
name: "share_enabled",
label: "Enable Sharing",
description: "Allow users to share checks via public links",
type: "Boolean",
value: "true",
enabled: true
},
{
name: "sso_enabled",
label: "Enable SSO",
description: "Enable Single Sign-On (SSO) authentication",
type: "Boolean",
value: "false",
enabled: true
},
{
name: "sso_protocol",
label: "SSO Protocol",
description: "Protocol used for SSO (oauth2 or saml)",
type: "String",
value: "oauth2",
enabled: true
},
{
name: "sso_entry_point",
label: "SSO Entry Point",
description: "URL of the Identity Provider (IdP) entry point",
type: "String",
value: "",
enabled: true
},
{
name: "sso_issuer",
label: "SSO Issuer",
description: "Entity ID of the Identity Provider",
type: "String",
value: "",
enabled: true
},
{
name: "sso_client_id",
label: "SSO Client ID",
description: "Client ID for OAuth2",
type: "String",
value: "",
enabled: true
}
];
// src/server/lib/AppSettings/AppSettings.ts
var AppSettings2 = class {
model;
cache;
lastFetch = 0;
TTL = 30 * 1e3;
// 30 seconds
constructor() {
this.model = AppSettings_model_default;
this.cache = null;
}
async init() {
await this.refreshCache();
return this;
}
async refreshCache() {
this.cache = await this.model.find().lean().exec();
this.lastFetch = Date.now();
}
async ensureInitialized() {
if (!this.cache) {
await this.refreshCache();
}
if (Date.now() - this.lastFetch > this.TTL) {
await this.refreshCache();
}
}
async count() {
await this.ensureInitialized();
return this.model.countDocuments().exec();
}
async loadInitialFromFile() {
await this.ensureInitialized();
for (const setting of initialAppSettings_default) {
await this.model.updateOne(
{ name: setting.name },
{ $setOnInsert: setting },
{ upsert: true }
);
}
await this.refreshCache();
}
async get(name) {
await this.ensureInitialized();
return this.cache.find((x) => x.name === name) || this.model.findOne({ name }).exec();
}
async set(name, value) {
await this.ensureInitialized();
const item = await this.model.findOneAndUpdate(
{ name },
{ value },
{ new: true }
).lean().exec();
if (!item) {
throw new Error(`Setting '${name}' not found`);
}
const cachedItem = this.cache.find((x) => x.name === name);
if (cachedItem) {
cachedItem["value"] = item.value;
}
}
async enable(name) {
await this.ensureInitialized();
const item = await this.model.findOneAndUpdate(
{ name },
{ enabled: true },
{ new: true }
).lean().exec();
if (!item) {
throw new Error(`Setting '${name}' not found`);
}
const cachedItem = this.cache.find((x) => x.name === name);
if (cachedItem) {
cachedItem["enabled"] = true;
}
}
async disable(name) {
await this.ensureInitialized();
const item = await this.model.findOneAndUpdate(
{ name },
{ enabled: false },
{ new: true }
).lean().exec();
if (!item) {
throw new Error(`Setting '${name}' not found`);
}
const cachedItem = this.cache.find((x) => x.name === name);
if (cachedItem) {
cachedItem["enabled"] = false;
}
}
async isAuthEnabled() {
await this.ensureInitialized();
const envOverride = process.env.SYNGRISI_AUTH_OVERRIDE;
if (typeof envOverride !== "undefined") {
if (envOverride === "true") {
return true;
}
}
const envAuth = process.env.SYNGRISI_AUTH;
if (envAuth === "true") {
return true;
}
if (envAuth === "false") {
return false;
}
return (await this.get("authentication"))?.value === "true";
}
async isFirstRun() {
await this.ensureInitialized();
return (await this.get("first_run"))?.value === "true";
}
};
var appSettings = new AppSettings2();
// src/server/envConfig.ts
import { cleanEnv, host, num, port, str, bool } from "envalid";
import crypto from "crypto";
import path from "path";
import dotenv from "dotenv";
dotenv.config({ quiet: true });
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = "production";
}
var env = cleanEnv(process.env, {
NODE_ENV: str({ choices: ["development", "production", "test"] }),
SYNGRISI_DB_URI: str({ default: "mongodb://127.0.0.1:27017/SyngrisiDb" }),
SYNGRISI_APP_PORT: port({ default: 3e3 }),
SYNGRISI_IMAGES_PATH: str({ default: path.join(process.cwd(), "./.snapshots-images") }),
SYNGRISI_DOM_SNAPSHOTS_PATH: str({ default: "" }),
// If empty, uses SYNGRISI_IMAGES_PATH
SYNGRISI_TMP_DIR: str({ default: path.join(process.cwd(), ".tmp") }),
SYNGRISI_ADMIN_DATA_JOBS_PATH: str({ default: path.join(process.cwd(), ".tmp", "admin-data-jobs") }),
SYNGRISI_ADMIN_DATA_JOBS_TTL_MS: num({ default: 24 * 60 * 60 * 1e3 }),
SYNGRISI_ADMIN_DATA_MAX_CONCURRENT_JOBS: num({ default: 1 }),
SYNGRISI_ADMIN_DATA_UPLOAD_MAX_SIZE_MB: num({ default: 10240 }),
SYNGRISI_HTTP_LOG: bool({ default: false }),
SYNGRISI_COVERAGE: bool({ default: false }),
SYNGRISI_HOSTNAME: host({ default: "localhost" }),
SYNGRISI_AUTH: bool({ default: true }),
SYNGRISI_TEST_MODE: bool({ default: false }),
SYNGRISI_DISABLE_FIRST_RUN: bool({ default: false }),
MONGODB_ROOT_USERNAME: str({ default: "" }),
MONGODB_ROOT_PASSWORD: str({ default: "" }),
LOGLEVEL: str({ choices: ["error", "warn", "info", "verbose", "debug", "silly"], default: "debug" }),
// Legacy tests expect 20 rows per page; keep default aligned for e2e
SYNGRISI_PAGINATION_SIZE: num({ default: 20 }),
SYNGRISI_DISABLE_DEV_CORS: bool({ default: true, devDefault: true }),
SYNGRISI_SESSION_STORE_KEY: str({ default: crypto.randomBytes(64).toString("hex") }),
SYNGRISI_LOG_LEVEL: str({ default: "debug" }),
SYNGRISI_DISABLE_LOGS: bool({ default: false }),
SYNGRISI_AUTO_REMOVE_CHECKS_POLL_INTERVAL_MS: num({ default: 10 * 60 * 1e3 }),
// 10 minutes
SYNGRISI_AUTO_REMOVE_CHECKS_MIN_INTERVAL_MS: num({ default: 24 * 60 * 60 * 1e3 }),
SYNGRISI_ENABLE_SCHEDULERS_IN_TEST_MODE: bool({ default: false }),
// RCA
SYNGRISI_RCA: bool({ default: false }),
// trunk features
SYNGRISI_TRUNK_FEATURE_AI_SEVERITY: bool({ default: false }),
SYNGRISI_AI_KEY: str({ default: "" }),
OPENAI_API_BASE_URL: str({ default: "https://api.openai.com/v1" }),
OPENAI_API_KEY: str({ default: "" }),
SYNGRISI_V8_COVERAGE_ON_EXIT: bool({ default: false }),
// Rate Limiting
SYNGRISI_RATE_LIMIT_WINDOW_MS: num({ default: 15 * 60 * 1e3 }),
// 15 minutes
SYNGRISI_RATE_LIMIT_MAX: num({ default: 5e4 }),
SYNGRISI_AUTH_RATE_LIMIT_WINDOW_MS: num({ default: 15 * 60 * 1e3 }),
// 15 minutes
SYNGRISI_AUTH_RATE_LIMIT_MAX: num({ default: 200 }),
// Mongo tuneables for tests/CI flake reduction
SYNGRISI_MONGO_SOCKET_TIMEOUT_MS: num({ default: 6e4 }),
SYNGRISI_MONGO_MAX_POOL_SIZE: num({ default: 20 }),
SYNGRISI_MONGO_MIN_POOL_SIZE: num({ default: 2 }),
SYNGRISI_MONGO_MAX_IDLE_TIME_MS: num({ default: 3e4 }),
SYNGRISI_MONGO_WAIT_QUEUE_TIMEOUT_MS: num({ default: 3e4 }),
SYNGRISI_MONGO_SERVER_SELECTION_TIMEOUT_MS: num({ default: 1e4 }),
SYNGRISI_MONGO_CONNECT_TIMEOUT_MS: num({ default: 3e4 }),
// SSO Configuration
SSO_ENABLED: bool({ default: false }),
SSO_PROTOCOL: str({ choices: ["", "oauth2", "saml"], default: "" }),
SSO_CLIENT_ID: str({ default: "" }),
SSO_CLIENT_SECRET: str({ default: "" }),
SSO_AUTHORIZATION_URL: str({ default: "" }),
SSO_TOKEN_URL: str({ default: "" }),
SSO_USERINFO_URL: str({ default: "" }),
SSO_CALLBACK_URL: str({ default: "/v1/auth/sso/oauth/callback" }),
// SAML specific
SSO_ENTRY_POINT: str({ default: "" }),
SSO_ISSUER: str({ default: "" }),
SSO_CERT: str({ default: "" }),
SSO_IDP_ISSUER: str({ default: "" }),
SSO_IDP_METADATA_URL: str({ default: "" }),
// URL to fetch IdP metadata XML (alternative to manual SSO_ENTRY_POINT/SSO_CERT)
// SSO user settings
SSO_DEFAULT_ROLE: str({ choices: ["", "user", "admin", "reviewer"], default: "reviewer" }),
SSO_AUTO_CREATE_USERS: bool({ default: true }),
SSO_ALLOW_ACCOUNT_LINKING: bool({ default: true }),
// Plugin System
SYNGRISI_PLUGINS_ENABLED: str({ default: "" }),
// Comma-separated list of enabled plugins
SYNGRISI_PLUGINS_DIR: str({ default: "" }),
// Directory for external plugins
// Okta Auth Plugin
// Deprecated: Use SYNGRISI_PLUGIN_JWT_AUTH_* variables instead
OKTA_JWKS_URL: str({ default: "" }),
OKTA_ISSUER: str({ default: "" }),
OKTA_SERVICE_USER_ROLE: str({ default: "" }),
OKTA_AUTH_HEADER: str({ default: "" }),
// Custom Check Validator Plugin
CHECK_MISMATCH_THRESHOLD: str({ default: "0" }),
// Mismatch % below which checks pass
CHECK_VALIDATOR_SCRIPT: str({ default: "" })
// Path to custom validation script
});
// src/server/controllers/settings.controller.ts
var getSettings = catchAsync_default(async (req, res) => {
const AppSettings3 = appSettings;
const result = AppSettings3.cache;
res.json(result);
});
var getPublicSettings = catchAsync_default(async (req, res) => {
const AppSettings3 = appSettings;
const result = AppSettings3.cache.filter((x) => ["share_enabled"].includes(x.name));
result.push({
name: "rca_enabled",
value: env.SYNGRISI_RCA,
enabled: true
});
res.json(result);
});
var updateSetting = catchAsync_default(async (req, res) => {
const AppSettings3 = appSettings;
const { name } = req.params;
await AppSettings3.set(name, req.body.value);
if (req.body.enabled === false) {
await AppSettings3.disable(name);
} else {
await AppSettings3.enable(name);
}
res.json({ message: "success" });
});
export {
getPublicSettings,
getSettings,
updateSetting
};
//# sourceMappingURL=settings.controller.js.map