Merge pull request #12 from JBB0807/auth

Authentication for students and instructors
This commit is contained in:
JB Balahadia 2025-05-05 10:16:10 -07:00 committed by GitHub
commit fd751e64ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 1401 additions and 255 deletions

View file

@ -0,0 +1,14 @@
NODE_ENV=development
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
#use this when testing local, remmber to run the proxy command
DATABASE_URL="postgresql://postgres:wly9H8gjjmxYfg1@localhost:15432/postgres?schema=public"
# DATABASE_URL="postgres://postgres:wly9H8gjjmxYfg1@bytecamp-db.flycast:5432?sslmode=disable"
# DATABASE_URL=postgres://snakebyte:zVB7lgOiKr89dq6@localhost:5432/snakebyte?sslmode=disable
NODE_PORT=3200

View file

@ -1,3 +1,5 @@
NODE_ENV=development
# Environment variables declared in this file are automatically made available to Prisma. # Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema # See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema

View file

@ -10,8 +10,24 @@ const port = process.env.NODE_PORT || 3000;
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
async function encryptPassword(password) {
if (!password) {
return null;
}
return new Promise((resolve, reject) => {
bcrypt.hash(password, 10, (err, hash) => {
if (err) {
reject(err);
} else {
resolve(hash);
}
});
});
}
//function to conver req.body to assignment //function to conver req.body to assignment
function convertToAssignment(req) { async function convertToAssignment(req) {
const { const {
campid, campid,
programid, programid,
@ -24,7 +40,8 @@ function convertToAssignment(req) {
instructorid instructorid
} = req.body; } = req.body;
// const hashedPassword = await bcrypt.hash(Password, 10); const hashPassword = await encryptPassword(req.body.password);
return { return {
campid: campid, campid: campid,
programid: programid, programid: programid,
@ -33,8 +50,7 @@ function convertToAssignment(req) {
originalfile: originalfile, originalfile: originalfile,
editablefile: editablefile, editablefile: editablefile,
assignmenturl: assignmenturl, assignmenturl: assignmenturl,
// passwordhash: hashedpassword, passwordhash: hashPassword,
passwordhash: password,
instructorid: instructorid, instructorid: instructorid,
}; };
} }
@ -44,23 +60,9 @@ app.post("/assignments", async (req, res) => {
try { try {
console.log("Request body:", req.body); console.log("Request body:", req.body);
// const { const assignment = await convertToAssignment(req);
// campid,
// programid,
// studentname,
// snakegameid,
// originalfile,
// editablefile,
// assignmenturl,
// password,
// instructorid
// } = req.body;
// const hashedPassword = await bcrypt.hash(Password, 10);
const newAssignment = await prisma.assignments.create({ const newAssignment = await prisma.assignments.create({
data: { data: assignment,
...convertToAssignment(req)
},
}); });
console.log("Assignment created successfully:", newAssignment); console.log("Assignment created successfully:", newAssignment);
@ -119,17 +121,11 @@ app.get("/assignments/:id", async (req, res) => {
app.put("/assignments/:id", async (req, res) => { app.put("/assignments/:id", async (req, res) => {
try { try {
const { id } = req.params; const { id } = req.params;
const data = req.body; const assignment = await convertToAssignment(req);
if (data.password) {
// data.passwordhash = await bcrypt.hash(data.Password, 10);
data.passwordhash = data.password;
delete data.password;
}
const updatedAssignment = await prisma.assignments.update({ const updatedAssignment = await prisma.assignments.update({
where: { assignmentid: parseInt(id) }, where: { assignmentid: parseInt(id) },
data, data: assignment,
}); });
res.json({ res.json({

View file

@ -7,26 +7,39 @@ app = 'db-assignment-service'
primary_region = 'sea' primary_region = 'sea'
[build] [build]
# Only needed if you're using a Dockerfile — can be empty if using buildpacks or Node preset
[http_service] # Removed the [http_service] section to disable public HTTP/HTTPS access
internal_port = 3000 # [http_service]
force_https = true # internal_port = 3000
auto_stop_machines = 'stop' # force_https = true
auto_start_machines = true # auto_stop_machines = true
min_machines_running = 0 # auto_start_machines = true
processes = ['app'] # min_machines_running = 0
# processes = ["app"]
[[services]] [[services]]
protocol = 'tcp' protocol = "tcp"
internal_port = 3000 internal_port = 3000
ports = [] internal_only = true # Makes this service only accessible internally
auto_start_machines = true
auto_stop_machines = true
# Removed public port exposure
# [[services.ports]]
# port = 80
# handlers = ["http"]
# [[services.ports]]
# port = 443
# handlers = ["tls", "http"]
[services.concurrency] [services.concurrency]
type = 'requests' type = "requests"
hard_limit = 1000 hard_limit = 1000
soft_limit = 500 soft_limit = 500
[[vm]] [[vm]]
memory = '1gb' memory = '1gb'
cpu_kind = 'shared' cpu_kind = "shared"
cpus = 1 cpus = 1

View file

@ -10,6 +10,9 @@
"express": "^5.1.0", "express": "^5.1.0",
"nodemon": "^3.1.9", "nodemon": "^3.1.9",
"prisma": "^6.1.0" "prisma": "^6.1.0"
},
"devDependencies": {
"dotenv-cli": "^8.0.0"
} }
}, },
"node_modules/@mapbox/node-pre-gyp": { "node_modules/@mapbox/node-pre-gyp": {
@ -377,6 +380,21 @@
"node": ">=6.6.0" "node": ">=6.6.0"
} }
}, },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
@ -418,6 +436,45 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/dotenv": {
"version": "16.5.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
"integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dotenv-cli": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-8.0.0.tgz",
"integrity": "sha512-aLqYbK7xKOiTMIRf1lDPbI+Y+Ip/wo5k3eyp6ePysVaSqbyxjyK3dK35BTxG+rmd7djf5q2UPs4noPNH+cj0Qw==",
"dev": true,
"license": "MIT",
"dependencies": {
"cross-spawn": "^7.0.6",
"dotenv": "^16.3.0",
"dotenv-expand": "^10.0.0",
"minimist": "^1.2.6"
},
"bin": {
"dotenv": "cli.js"
}
},
"node_modules/dotenv-expand": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz",
"integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/dunder-proto": { "node_modules/dunder-proto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@ -912,6 +969,13 @@
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"license": "ISC"
},
"node_modules/make-dir": { "node_modules/make-dir": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@ -999,6 +1063,16 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/minipass": { "node_modules/minipass": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
@ -1211,6 +1285,16 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "8.2.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
@ -1454,6 +1538,29 @@
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/side-channel": { "node_modules/side-channel": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
@ -1713,6 +1820,22 @@
"webidl-conversions": "^3.0.0" "webidl-conversions": "^3.0.0"
} }
}, },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/wide-align": { "node_modules/wide-align": {
"version": "1.1.5", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",

View file

@ -8,6 +8,9 @@
}, },
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon app.js" "dev": "dotenv -e .env.development nodemon app.js"
},
"devDependencies": {
"dotenv-cli": "^8.0.0"
} }
} }

View file

@ -1,2 +1,3 @@
#DB_ASSIGNMENT_SERVICE_URL = "http://localhost:3000" #DB_ASSIGNMENT_SERVICE_URL = "http://localhost:3000"
DB_ASSIGNMENT_SERVICE_URL = "http://db-assignment-service.internal:3000" DB_ASSIGNMENT_SERVICE_URL = "http://db-assignment-service.internal:3000"
NODE_PORT = 8080

View file

@ -0,0 +1,6 @@
NODE_ENV=development
#DB_ASSIGNMENT_SERVICE_URL="http://localhost:3000"
DB_ASSIGNMENT_SERVICE_URL="http://localhost:3200"
AUTH_SESSION_KEY="f3f4d8e6b17a4b3abdc8e9a2c0457aaf91c0d5f6e3b7a9c8df624bd71ea35f42"
ACCEPTED_ORIGINS=http://localhost:3000,http://localhost:8081,http://localhost:3001,http://localhost:5173
NODE_PORT=8082

View file

@ -0,0 +1,6 @@
NODE_ENV=development
#DB_ASSIGNMENT_SERVICE_URL="http://localhost:3000"
DB_ASSIGNMENT_SERVICE_URL="http://js-assignment-db-service:3200"
AUTH_SESSION_KEY="f3f4d8e6b17a4b3abdc8e9a2c0457aaf91c0d5f6e3b7a9c8df624bd71ea35f42"
ACCEPTED_ORIGINS=http://localhost:3000,http://localhost:8081,http://localhost:3001,http://localhost:5173
NODE_PORT=8082

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@
"version": "1.0.0", "version": "1.0.0",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
"dev": "nodemon server.js" "dev": "dotenv -e .env.development nodemon server.js"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
@ -12,11 +12,16 @@
"dependencies": { "dependencies": {
"aws-sdk": "^2.1692.0", "aws-sdk": "^2.1692.0",
"axios": "^1.9.0", "axios": "^1.9.0",
"bcrypt": "^5.1.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.5.0", "dotenv": "^16.5.0",
"express": "^5.1.0", "express": "^5.1.0",
"express-session": "^1.18.1", "express-session": "^1.18.1",
"multer": "^1.4.5-lts.2",
"nodemon": "^3.1.9", "nodemon": "^3.1.9",
"passport": "^0.7.0" "passport": "^0.7.0"
},
"devDependencies": {
"dotenv-cli": "^8.0.0"
} }
} }

View file

@ -1,15 +1,42 @@
const intructorRouter = require("express").Router(); const intructorRouter = require("express").Router();
const passport = require("passport"); const passport = require("passport");
const axios = require("axios"); const axios = require("axios");
const multer = require('multer');
const FormData = require('form-data');
const DB_ASSIGNMENT_SERVICE_URL = process.env.DB_ASSIGNMENT_SERVICE_URL || "http://localhost:3000";
const DB_ASSIGNMENT_SERVICE_URL =
process.env.DB_ASSIGNMENT_SERVICE_URL || "http://localhost:3000";
console.log("DB_ASSIGNMENT_SERVICE_URL:", DB_ASSIGNMENT_SERVICE_URL); console.log("DB_ASSIGNMENT_SERVICE_URL:", DB_ASSIGNMENT_SERVICE_URL);
// Use memory storage to keep file in RAM
const upload = multer({ storage: multer.memoryStorage() });
// This endpoint is for instructors to create a new assignment // This endpoint is for instructors to create a new assignment
intructorRouter.post("/create", intructorRouter.post("/create",
upload.single('file'),
// passport.authenticate("jwt", { session: false }), // passport.authenticate("jwt", { session: false }),
async (req, res) => { async (req, res) => {
try { try {
const file = req.file;
const assignmentData = req.body;
if (!file) {
return res.status(400).send('No file uploaded.');
}
await axios.post('https://target-api.com/endpoint', {
filename: file.originalname,
mimetype: file.mimetype,
content: file.buffer.toString('base64')
}, {
headers: {
'Content-Type': 'application/json'
}
});
console.log("Creating a new assignment with data:", req.body); console.log("Creating a new assignment with data:", req.body);
const response = await axios.post(`${DB_ASSIGNMENT_SERVICE_URL}/assignments`, req.body); const response = await axios.post(`${DB_ASSIGNMENT_SERVICE_URL}/assignments`, req.body);
console.log("Response from DB_ASSIGNMENT_SERVICE_URL:", response.data); console.log("Response from DB_ASSIGNMENT_SERVICE_URL:", response.data);
@ -18,14 +45,18 @@ intructorRouter.post("/create",
console.error("Error creating assignment:", error.message); console.error("Error creating assignment:", error.message);
res.status(error.response?.status || 500).json({ error: error.message }); res.status(error.response?.status || 500).json({ error: error.message });
} }
}); }
);
// This endpoint is for instructors to get details of a specific assignment // This endpoint is for instructors to get details of a specific assignment
intructorRouter.get("/details/:id", async (req, res) => { intructorRouter.get("/details/:id", async (req, res) => {
try { try {
const assignmentId = req.params.id; const assignmentId = req.params.id;
console.log("Fetching details for assignmentId:", assignmentId); console.log("Fetching details for assignmentId:", assignmentId);
const response = await axios.get(`${DB_ASSIGNMENT_SERVICE_URL}/assignments/${assignmentId}`); const response = await axios.get(
`${DB_ASSIGNMENT_SERVICE_URL}/assignments/${assignmentId}`
);
console.log("Response from DB_ASSIGNMENT_SERVICE_URL:", response.data); console.log("Response from DB_ASSIGNMENT_SERVICE_URL:", response.data);
res.status(response.status).json(response.data); res.status(response.status).json(response.data);
} catch (error) { } catch (error) {
@ -35,13 +66,18 @@ intructorRouter.get("/details/:id", async (req, res) => {
}); });
// This endpoint is for instructors to get a list of assignments they have created // This endpoint is for instructors to get a list of assignments they have created
intructorRouter.get("/list/:id", async (req, res) => { intructorRouter.get(
"/list/:id",
// passport.authenticate("jwt", { session: false }),
async (req, res) => {
// if (req.isAuthenticated()) { // if (req.isAuthenticated()) {
try { try {
const instructorId = req.params.id; const instructorId = req.params.id;
console.log("Fetching assignments for instructorId:", instructorId); console.log("Fetching assignments for instructorId:", instructorId);
// const instructorId = req.user.userid; // Assuming req.user contains the authenticated user // const instructorId = req.user.userid; // Assuming req.user contains the authenticated user
const response = await axios.get(`${DB_ASSIGNMENT_SERVICE_URL}/assignments/instructor/${instructorId}`); const response = await axios.get(
`${DB_ASSIGNMENT_SERVICE_URL}/assignments/instructor/${instructorId}`
);
console.log("Response from DB_ASSIGNMENT_SERVICE_URL:", response.data); console.log("Response from DB_ASSIGNMENT_SERVICE_URL:", response.data);
res.status(response.status).json(response.data); res.status(response.status).json(response.data);
} catch (error) { } catch (error) {
@ -50,33 +86,43 @@ intructorRouter.get("/list/:id", async (req, res) => {
// } else { // } else {
// return res.status(401).json({ error: "Not authenticated" }); // return res.status(401).json({ error: "Not authenticated" });
// } // }
}
}); );
// This endpoint is for instructors to update an assignment // This endpoint is for instructors to update an assignment
intructorRouter.put("/update/:id", intructorRouter.put(
"/update/:id",
// passport.authenticate("jwt", { session: false }), // passport.authenticate("jwt", { session: false }),
async (req, res) => { async (req, res) => {
try { try {
const assignmentId = req.params.id; const assignmentId = req.params.id;
const response = await axios.put(`${DB_ASSIGNMENT_SERVICE_URL}/assignments/${assignmentId}`, req.body);
const response = await axios.put(
`${DB_ASSIGNMENT_SERVICE_URL}/assignments/${assignmentId}`,
req.body
);
res.status(response.status).json(response.data); res.status(response.status).json(response.data);
} catch (error) { } catch (error) {
res.status(error.response?.status || 500).json({ error: error.message }); res.status(error.response?.status || 500).json({ error: error.message });
} }
}); }
);
// This endpoint is for instructors to delete an assignment // This endpoint is for instructors to delete an assignment
intructorRouter.delete("/delete/:id", intructorRouter.delete(
"/delete/:id",
// passport.authenticate("jwt", { session: false }), // passport.authenticate("jwt", { session: false }),
async (req, res) => { async (req, res) => {
try { try {
const assignmentId = req.params.id; const assignmentId = req.params.id;
const response = await axios.delete(`${DB_ASSIGNMENT_SERVICE_URL}/assignments/${assignmentId}`); const response = await axios.delete(
`${DB_ASSIGNMENT_SERVICE_URL}/assignments/${assignmentId}`
);
res.status(response.status).json(response.data); res.status(response.status).json(response.data);
} catch (error) { } catch (error) {
res.status(error.response?.status || 500).json({ error: error.message }); res.status(error.response?.status || 500).json({ error: error.message });
} }
}); }
);
module.exports = intructorRouter; module.exports = intructorRouter;

View file

@ -1,13 +1,56 @@
const studentRouter = require("express").Router(); const studentRouter = require("express").Router();
const passport = require("passport"); const passport = require("passport");
const axios = require("axios"); const axios = require("axios");
const bcrypt = require("bcrypt");
require("dotenv").config();
const DB_ASSIGNMENT_SERVICE_URL = process.env.DB_ASSIGNMENT_SERVICE_URL;
studentRouter.post("/save", (req, res) => {
}); studentRouter.post("/save", (req, res) => {});
studentRouter.post("/deploy", (req, res) => { studentRouter.post("/deploy", (req, res) => {});
studentRouter.post("/verify", async (req, res) => {
try {
const assignmentId = req.body.assignmentId;
const password = req.body.password;
console.log("Received request to verify assignment.");
console.log("Request body:", req.body);
console.log(
"Accessing assignment with ID:",
assignmentId,
"and password:",
password
);
console.log(`Fetching from URL: ${DB_ASSIGNMENT_SERVICE_URL}/assignments/${assignmentId}`);
const response = await axios.get(
`${DB_ASSIGNMENT_SERVICE_URL}/assignments/${assignmentId}`
);
console.log("Response from DB_ASSIGNMENT_SERVICE_URL:", response.data);
console.log("Password provided:", password);
console.log("Password hash from database:", response.data.passwordhash);
const isPasswordValid = await bcrypt.compare(
password,
response.data.passwordhash
);
console.log("Password validation result:", isPasswordValid);
if (!isPasswordValid || !response.data) {
console.log("Invalid id or password.");
return res.status(401).json({ error: "Invalid id and password" });
}
console.log("Verification successful. Sending response.");
res.status(response.status).json(response.data);
} catch (error) {
console.error("Error fetching assignment details:", error.message);
console.error("Error details:", error);
res.status(error.response?.status || 500).json({ error: error.message });
}
}); });
module.exports = studentRouter; module.exports = studentRouter;

View file

@ -70,5 +70,5 @@ app.get("/notebook/:appName", async (req, res) => {
} }
}); });
const port = process.env.PORT || 8080; const port = process.env.NODE_PORT || 8080;
app.listen(port, "0.0.0.0", () => console.log(`Listening on 0.0.0.0:${port}...`)); app.listen(port, "0.0.0.0", () => console.log(`Listening on 0.0.0.0:${port}...`));

View file

@ -6,6 +6,7 @@ ACCEPTED_ORIGINS ="https://bytecamp-web.fly.dev,https://byte-camp-auth-service.f
#DB_USER_SERVICE_URL = "http://localhost:3000/" #DB_USER_SERVICE_URL = "http://localhost:3000/"
DB_USER_SERVICE_URL = "http://db-user-service.internal:3000/" DB_USER_SERVICE_URL = "http://db-user-service.internal:3000/"
AUTH_SESSION_KEY = "f3f4d8e6b17a4b3abdc8e9a2c0457aaf91c0d5f6e3b7a9c8df624bd71ea35f42" AUTH_SESSION_KEY = "f3f4d8e6b17a4b3abdc8e9a2c0457aaf91c0d5f6e3b7a9c8df624bd71ea35f42"
ASSIGNMENT_SERVICE_URL="http://assignment-service.internal:8080"
# fly secrets set GOOGLE_CALLBACK_URL=https://byte-camp-auth-service.fly.dev/auth/google/callback # fly secrets set GOOGLE_CALLBACK_URL=https://byte-camp-auth-service.fly.dev/auth/google/callback
#fly secrets set GOOGLE_CLIENT_ID=485880105639-1in8tvb6ondnn198rasuj2d8ank06ntp.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=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 LOGIN_REDIRECT_URL=https://bytecamp-web.fly.dev/ DB_USER_SERVICE_URL=https://db-user-service.fly.dev:3000/ AUTH_SESSION_KEY=f3f4d8e6b17a4b3abdc8e9a2c0457aaf91c0d5f6e3b7a9c8df624bd71ea35f42 #fly secrets set GOOGLE_CLIENT_ID=485880105639-1in8tvb6ondnn198rasuj2d8ank06ntp.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=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 LOGIN_REDIRECT_URL=https://bytecamp-web.fly.dev/ DB_USER_SERVICE_URL=https://db-user-service.fly.dev:3000/ AUTH_SESSION_KEY=f3f4d8e6b17a4b3abdc8e9a2c0457aaf91c0d5f6e3b7a9c8df624bd71ea35f42

View file

@ -1,8 +1,11 @@
NODE_ENV=development
GOOGLE_CLIENT_ID="485880105639-1in8tvb6ondnn198rasuj2d8ank06ntp.apps.googleusercontent.com" GOOGLE_CLIENT_ID="485880105639-1in8tvb6ondnn198rasuj2d8ank06ntp.apps.googleusercontent.com"
GOOGLE_CLIENT_SECRET="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" GOOGLE_CLIENT_SECRET="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"
GOOGLE_CALLBACK_URL="http://localhost:8080/auth/google/callback" GOOGLE_CALLBACK_URL="http://localhost:8080/auth/google/callback"
LOGIN_REDIRECT_URL="http://localhost:5173/" LOGIN_REDIRECT_URL="http://localhost:5173/"
ACCEPTED_ORIGINS=http://localhost:3000,http://localhost:8081,http://localhost:3001,http://localhost:5173 ACCEPTED_ORIGINS=http://localhost:3000,http://localhost:8081,http://localhost:3001,http://localhost:5173
DB_USER_SERVICE_URL="http://js-user-db-service:3100/" ASSIGNMENT_SERVICE_URL="http://localhost:8082/"
DB_USER_SERVICE_URL="http://localhost:3100/"
AUTH_SESSION_KEY="f3f4d8e6b17a4b3abdc8e9a2c0457aaf91c0d5f6e3b7a9c8df624bd71ea35f42" AUTH_SESSION_KEY="f3f4d8e6b17a4b3abdc8e9a2c0457aaf91c0d5f6e3b7a9c8df624bd71ea35f42"
PORT=8080 PORT=8080

View file

@ -16,7 +16,11 @@
"express-session": "^1.18.1", "express-session": "^1.18.1",
"nodemon": "^3.1.9", "nodemon": "^3.1.9",
"passport": "^0.7.0", "passport": "^0.7.0",
"passport-custom": "^1.1.1",
"passport-google-oauth20": "^2.0.0" "passport-google-oauth20": "^2.0.0"
},
"devDependencies": {
"dotenv-cli": "^8.0.0"
} }
}, },
"node_modules/accepts": { "node_modules/accepts": {
@ -280,6 +284,21 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "2.6.9", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -325,6 +344,32 @@
"url": "https://dotenvx.com" "url": "https://dotenvx.com"
} }
}, },
"node_modules/dotenv-cli": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-8.0.0.tgz",
"integrity": "sha512-aLqYbK7xKOiTMIRf1lDPbI+Y+Ip/wo5k3eyp6ePysVaSqbyxjyK3dK35BTxG+rmd7djf5q2UPs4noPNH+cj0Qw==",
"dev": true,
"license": "MIT",
"dependencies": {
"cross-spawn": "^7.0.6",
"dotenv": "^16.3.0",
"dotenv-expand": "^10.0.0",
"minimist": "^1.2.6"
},
"bin": {
"dotenv": "cli.js"
}
},
"node_modules/dotenv-expand": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz",
"integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/dunder-proto": { "node_modules/dunder-proto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@ -847,6 +892,13 @@
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"license": "ISC"
},
"node_modules/math-intrinsics": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@ -910,6 +962,16 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -1063,6 +1125,18 @@
"url": "https://github.com/sponsors/jaredhanson" "url": "https://github.com/sponsors/jaredhanson"
} }
}, },
"node_modules/passport-custom": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/passport-custom/-/passport-custom-1.1.1.tgz",
"integrity": "sha512-/2m7jUGxmCYvoqenLB9UrmkCgPt64h8ZtV+UtuQklZ/Tn1NpKBeOorCYkB/8lMRoiZ5hUrCoMmDtxCS/d38mlg==",
"license": "MIT",
"dependencies": {
"passport-strategy": "1.x.x"
},
"engines": {
"node": ">= 0.10.0"
}
},
"node_modules/passport-google-oauth20": { "node_modules/passport-google-oauth20": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz", "resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz",
@ -1103,6 +1177,16 @@
"node": ">= 0.4.0" "node": ">= 0.4.0"
} }
}, },
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "8.2.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
@ -1345,6 +1429,29 @@
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/side-channel": { "node_modules/side-channel": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
@ -1545,6 +1652,22 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/wrappy": { "node_modules/wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",

View file

@ -3,7 +3,7 @@
"version": "1.0.0", "version": "1.0.0",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
"dev": "nodemon server.js" "dev": "dotenv -e .env.development nodemon server.js"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
@ -17,6 +17,10 @@
"express-session": "^1.18.1", "express-session": "^1.18.1",
"nodemon": "^3.1.9", "nodemon": "^3.1.9",
"passport": "^0.7.0", "passport": "^0.7.0",
"passport-custom": "^1.1.1",
"passport-google-oauth20": "^2.0.0" "passport-google-oauth20": "^2.0.0"
},
"devDependencies": {
"dotenv-cli": "^8.0.0"
} }
} }

View file

@ -1,7 +1,9 @@
require('dotenv').config(); require("dotenv").config();
const GoogleStrategy = require("passport-google-oauth20").Strategy; const GoogleStrategy = require("passport-google-oauth20").Strategy;
const passport = require("passport"); const passport = require("passport");
const CustomStrategy = require("passport-custom").Strategy;
const axios = require("axios");
passport.use( passport.use(
new GoogleStrategy( new GoogleStrategy(
@ -12,25 +14,65 @@ passport.use(
scope: ["profile", "email"], scope: ["profile", "email"],
}, },
function (accessToken, refreshToken, profile, callback) { function (accessToken, refreshToken, profile, callback) {
callback(null, {...profile, role: "instructor"});
// Save the user info to your DB here if still not yet saved
// Example of what profile might contain:
// {
// "id": "112233445566778899",
// "displayName": "John Doe",
// "emails": [{ "value": "john.doe@gmail.com" }],
// "photos": [{ "value": "https://.../photo.jpg" }]
// }
callback(null, profile);
} }
) )
); );
passport.use(
"student-auth",
new CustomStrategy(async (req, done) => {
const { assignmentId, password } = req.body;
console.log("Custom strategy invoked");
console.log("Received assignmentId:", assignmentId);
console.log("Received password:", password);
try {
console.log("Sending request to external auth service...");
const response = await axios.post(
`${process.env.ASSIGNMENT_SERVICE_URL}/student/verify`,
{
assignmentId,
password,
}
);
if (response.status === 200 && response.data) {
user = {
...response.data,
role: "student",
};
console.log("Authentication successful, user:", user);
return done(null, user); // success
} else {
console.log("Authentication failed: Invalid credentials");
return done(null, false, { message: "Invalid credentials" });
}
} catch (err) {
console.error("Error during authentication:", err);
return done(err);
}
})
);
passport.serializeUser((user, done) => { passport.serializeUser((user, done) => {
done(null, user); // done(null, user);
console.log("Serializing user:", user);
done(null, {
id: user.assignmentid || user.id,
displayName: user.studentname || user.displayName,
role: user.role,
emails: user.emails || "none",
});
}); });
passport.deserializeUser((user, done) => { passport.deserializeUser(async (user, done) => {
try {
console.log("Deserializing user:", user);
done(null, user); done(null, user);
} catch (err) {
console.error("Error during deserialization:", err);
done(err);
}
}); });

View file

@ -11,6 +11,8 @@ router.get(
); );
router.get("/current_user", (req, res) => { router.get("/current_user", (req, res) => {
console.log("Current user endpoint hit");
console.log("Request user:", req.user);
if (req.isAuthenticated()) { if (req.isAuthenticated()) {
console.log("Authenticated user:", req.user); console.log("Authenticated user:", req.user);
res.json(req.user); res.json(req.user);
@ -52,9 +54,47 @@ router.get("/login/failed", (req, res) => {
router.get("/google", passport.authenticate("google", ["profile", "email"])); router.get("/google", passport.authenticate("google", ["profile", "email"]));
router.post(
"/student/login",
passport.authenticate("student-auth"),
(req, res) => {
console.log("Student login endpoint hit");
if (req.user) {
console.log("Authenticated user:", req.user);
console.log("Processing student login...");
// Optional: augment user object (doesn't affect session unless you reserialize)
req.user.userId = req.user.assignmentid;
req.user.role = "student";
req.logIn(req.user, function(err) {
if (err) return next(err);
console.log('is authenticated?: ' + req.isAuthenticated());
return res.status(200).json({
success: true,
message: 'Successful Login',
user: req.user
});
});
} else {
console.log("Authentication failed");
res.status(401).json({ error: true, message: "Authentication failed" });
}
}
);
router.get("/logout", (req, res) => { router.get("/logout", (req, res) => {
req.logOut();
req.logout((err) => {
if (err) {
return next(err);
}
res.redirect(process.env.LOGIN_REDIRECT_URL); res.redirect(process.env.LOGIN_REDIRECT_URL);
}); });
});
module.exports = router; module.exports = router;

View file

@ -6,10 +6,14 @@ const passport = require("passport");
const passportSetup = require("./passport"); const passportSetup = require("./passport");
const authRoute = require("./routes/auth"); const authRoute = require("./routes/auth");
const session = require("express-session"); const session = require("express-session");
const bodyParser = require("body-parser");
const app = express(); const app = express();
app.use(bodyParser.json()); // or express.json()
app.use(bodyParser.urlencoded({ extended: true }));
app.use( app.use(
session({ session({
secret: process.env.AUTH_SESSION_KEY, secret: process.env.AUTH_SESSION_KEY,
resave: false, resave: false,
@ -31,6 +35,8 @@ app.use(
}) })
) )
app.use(express.json());
app.use("/auth", authRoute); app.use("/auth", authRoute);
const port = process.env.PORT || 8080; const port = process.env.PORT || 8080;

View file

@ -11,7 +11,7 @@ services:
ports: ports:
- "3200:3200" # Expose port to the same host - "3200:3200" # Expose port to the same host
env_file: env_file:
- ./assignment-db-service/dev.env - ./assignment-db-service/.env.development
networks: networks:
- backend - backend
@ -25,7 +25,7 @@ services:
ports: ports:
- "8082:8082" # Expose port to the same host - "8082:8082" # Expose port to the same host
env_file: env_file:
- ./assignment-service/dev.env - ./assignment-service/.env.development
depends_on: depends_on:
- js-assignment-db-service - js-assignment-db-service
networks: networks:
@ -41,7 +41,7 @@ services:
ports: ports:
- "8080:8080" # Expose port to the same host - "8080:8080" # Expose port to the same host
env_file: env_file:
- ./auth-service/dev.env - ./auth-service/.env.development
networks: networks:
- backend - backend
@ -55,7 +55,7 @@ services:
ports: ports:
- "3100:3100" # Expose port to the same host - "3100:3100" # Expose port to the same host
env_file: env_file:
- ./user-db-service/dev.env - ./user-db-service/.env.development
networks: networks:
- backend - backend

View file

@ -5,8 +5,8 @@
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings # See the documentation for all the connection string options: https://pris.ly/d/connection-strings
#use this when testing local, remmber to run the proxy command #use this when testing local, remmber to run the proxy command
# DATABASE_URL="postgresql://postgres:wly9H8gjjmxYfg1@localhost:15432/postgres?schema=public" DATABASE_URL="postgresql://postgres:wly9H8gjjmxYfg1@localhost:15432/postgres?schema=public"
DATABASE_URL="postgres://postgres:wly9H8gjjmxYfg1@bytecamp-db.flycast:5432?sslmode=disable" # DATABASE_URL="postgres://postgres:wly9H8gjjmxYfg1@bytecamp-db.flycast:5432?sslmode=disable"
# DATABASE_URL=postgres://snakebyte:zVB7lgOiKr89dq6@localhost:5432/snakebyte?sslmode=disable # DATABASE_URL=postgres://snakebyte:zVB7lgOiKr89dq6@localhost:5432/snakebyte?sslmode=disable
NODE_PORT=3000 NODE_PORT=3100

View file

@ -6,11 +6,6 @@ primary_region = "sea"
[build] [build]
# Only needed if you're using a Dockerfile — can be empty if using buildpacks or Node preset # Only needed if you're using a Dockerfile — can be empty if using buildpacks or Node preset
#[env]
#NODE_ENV = "production"
#DATABASE_URL = "postgresql://user:password@hostname:port/dbname"
# you can also leave DATABASE_URL unset here and use secrets instead
# Removed the [http_service] section to disable public HTTP/HTTPS access # Removed the [http_service] section to disable public HTTP/HTTPS access
# [http_service] # [http_service]
# internal_port = 3000 # internal_port = 3000
@ -24,6 +19,8 @@ primary_region = "sea"
protocol = "tcp" protocol = "tcp"
internal_port = 3000 internal_port = 3000
internal_only = true # Makes this service only accessible internally internal_only = true # Makes this service only accessible internally
auto_start_machines = true
auto_stop_machines = true
# Removed public port exposure # Removed public port exposure
# [[services.ports]] # [[services.ports]]