Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
59fab83
setup tests
justinnguyen0 Nov 11, 2023
96455bc
Merge pull request #1 from CSE-210-Team-6/justin-tests
justinnguyen0 Nov 11, 2023
262cb0b
setup tests
justinnguyen0 Nov 11, 2023
c6b3b9f
Merge pull request #2 from CSE-210-Team-6/justin-tests
justinnguyen0 Nov 11, 2023
5c46a91
add first test
justinnguyen0 Nov 11, 2023
3ad97dc
Merge pull request #3 from CSE-210-Team-6/justin-tests
justinnguyen0 Nov 11, 2023
c1b434e
add more tests
justinnguyen0 Nov 12, 2023
f3ff5db
Merge pull request #4 from CSE-210-Team-6/justin-tests
justinnguyen0 Nov 12, 2023
702926d
add more tests
justinnguyen0 Nov 12, 2023
a2f64f4
Merge pull request #5 from CSE-210-Team-6/justin-tests
justinnguyen0 Nov 12, 2023
bab8692
CI-CD Workflow
kashishvjain Nov 12, 2023
2f64f75
Update test.yml
kashishvjain Nov 12, 2023
4ec4c04
Merge pull request #10 from CSE-210-Team-6/kashishvjain-patch-1
kashishvjain Nov 12, 2023
99b77d0
add more tests
justinnguyen0 Nov 12, 2023
1009b78
Merge pull request #12 from CSE-210-Team-6/justin-tests
justinnguyen0 Nov 12, 2023
534f92f
refactor for jest to pass
justinnguyen0 Nov 12, 2023
0e777db
Merge pull request #13 from CSE-210-Team-6/justin-tests
justinnguyen0 Nov 12, 2023
1453359
add more tests for storage.js
justinnguyen0 Nov 12, 2023
e8658aa
Merge pull request #14 from CSE-210-Team-6/justin-tests
justinnguyen0 Nov 12, 2023
df85684
add more tests for storage.js
justinnguyen0 Nov 12, 2023
51f643d
Merge pull request #15 from CSE-210-Team-6/justin-tests
justinnguyen0 Nov 13, 2023
3741ace
test: add tests for notes.js
Melody-Creator Nov 13, 2023
c741d6a
Merge pull request #16 from CSE-210-Team-6/test
Melody-Creator Nov 16, 2023
66fa56d
Kashish reformat (#17)
kashishvjain Nov 17, 2023
2847b55
add more tests
justinnguyen0 Nov 18, 2023
cca7681
Merge pull request #18 from CSE-210-Team-6/justin-tests
justinnguyen0 Nov 18, 2023
6b756b8
add jsdocs
ekdnam Nov 18, 2023
3bae377
add basicauth object
ekdnam Nov 18, 2023
5f92df8
refactor account.js
ekdnam Nov 18, 2023
206e1dc
add jsdocs to webfiger.js
ekdnam Nov 18, 2023
0ba959e
camel case
ekdnam Nov 18, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ module.exports = {
sourceType: 'module'
},
rules: {
}
},
ignorePatterns: ["**/*.jest.js"],
}
25 changes: 25 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Unit Tests

on: [push, pull_request]

jobs:
tests:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [18.x, 16.x]

steps:
- uses: actions/checkout@v2

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}

- name: Install dependencies
run: npm install

- name: Run tests
run: npm test
3 changes: 3 additions & 0 deletions babel.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
presets: [['@babel/preset-env', {targets: {node: 'current'}}]],
};
292 changes: 237 additions & 55 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,172 @@
import express from 'express';
import { create } from 'express-handlebars';
// index.js is the start point for our project

// middleware to parse request bodies in different formats (e.g., JSON)
import bodyParser from 'body-parser';

// middleware to parse cookies in incoming requests
import cookieParser from 'cookie-parser';

// Cross-Origin Resource Sharing (CORS) middleware for enabling cross-origin requests
import cors from 'cors';

// the dotenv module for loading environment variables from a .env file
import dotenv from 'dotenv';

import bodyParser from 'body-parser';
import cors from 'cors';
import http from 'http';
// the Express.js framework for building web applications
import express from 'express';

// middleware for implementing basic authentication in Express
import basicAuth from 'express-basic-auth';

// the Handlebars view engine for rendering dynamic HTML content
import { create } from 'express-handlebars';

// the built-in Node.js HTTP module for creating an HTTP server
import http from 'http';

// the Moment.js library for handling dates and times
import moment from 'moment';

import { ActivityPub } from './lib/ActivityPub.js';
import { ensureAccount } from './lib/account.js';
import { account, webfinger, inbox, outbox, admin, notes, publicFacing } from './routes/index.js';

import { UserProfileRouter, WebfingerRouter, inbox, outbox, admin, notes, publicFacing } from './routes/index.js';

// load process.env from .env file
dotenv.config();
const { USERNAME, PASS, DOMAIN, PORT } = process.env;
['USERNAME', 'PASS', 'DOMAIN'].forEach(required => {
if (!process.env[required]) {
console.error(`Missing required environment variable: \`${required}\`. Exiting.`);
process.exit(1);
}
});

const envVariables = ['USERNAME', 'PASS', 'DOMAIN'];
const PATH_TO_TEMPLATES = './design';

/**
* Check the existence of required environment variables.
*
* @param {string[]} env_variables - An array of environment variable names that are required.
* @throws {Error} Throws an error and exits the process if any required environment variable is missing.
*/
function checkRequiredEnvironmentVariables(envVariables) {
envVariables.forEach(reqdVariable => {
/**
* Check if the required environment variable is missing.
* If missing, log an error message and exit the process.
*
* @example
* // Example usage:
* checkRequiredEnvironmentVariables(['PORT', 'DATABASE_URL']);
*/
if (!process.env[reqdVariable]) {
console.error(`Missing required environment variable: \`${reqdVariable}\`. Exiting.`);
process.exit(1);
}
});
}
checkRequiredEnvironmentVariables(envVariables);

const app = express();
/**
* Handlebars helper functions for custom template rendering.
*
* @typedef {Object} HandlebarsHelpers
* @property {Function} isVideo - Check if a string contains 'video' and execute the provided block if true.
* @property {Function} isImage - Check if a string contains 'image' and execute the provided block if true.
* @property {Function} isEq - Check if two values are equal and execute the provided block if true.
* @property {Function} or - Logical OR between two values.
* @property {Function} timesince - Format a date to show the time elapsed since the specified date.
* @property {Function} getUsername - Get the username using the ActivityPub module.
* @property {Function} stripProtocol - Remove 'https://' from the beginning of a string.
* @property {Function} stripHTML - Remove HTML tags from a string.
*/

/**
* Create an instance of Handlebars with custom helpers.
*
* @type {Handlebars}
* @see {@link https://handlebarsjs.com/api-reference/helpers.html}
*/
const hbs = create({
helpers: {
/**
* Check if a string contains 'video' and execute the provided block if true.
* @function
* @param {string} str - The string to check.
* @param {Object} options - Handlebars options object.
* @returns {string} - The rendered block if the condition is true.
*/
isVideo: (str, options) => {
if (str && str.includes('video')) return options.fn(this);
},

/**
* Check if a string contains 'image' and execute the provided block if true.
* @function
* @param {string} str - The string to check.
* @param {Object} options - Handlebars options object.
* @returns {string} - The rendered block if the condition is true.
*/
isImage: (str, options) => {
if (str && str.includes('image')) return options.fn(this);
},

/**
* Check if two values are equal and execute the provided block if true.
* @function
* @param {*} a - The first value.
* @param {*} b - The second value.
* @param {Object} options - Handlebars options object.
* @returns {string} - The rendered block if the condition is true.
*/
isEq: (a, b, options) => {
// eslint-disable-next-line
if (a == b) return options.fn(this);
},

/**
* Logical OR between two values.
* @function
* @param {*} a - The first value.
* @param {*} b - The second value.
* @param {Object} options - Handlebars options object.
* @returns {*} - The result of the logical OR operation.
*/
or: (a, b, options) => {
return a || b;
},

/**
* Format a date to show the time elapsed since the specified date.
* @function
* @param {Date} date - The date to be formatted.
* @returns {string} - The formatted time elapsed string.
*/
timesince: date => {
return moment(date).fromNow();
},

/**
* Get the username using the ActivityPub module.
* @function
* @param {*} user - The user object.
* @returns {string} - The username.
*/
getUsername: user => {
return ActivityPub.getUsername(user);
},

/**
* Remove 'https://' from the beginning of a string.
* @function
* @param {string} str - The string to process.
* @returns {string} - The string with 'https://' removed.
*/
stripProtocol: str => str.replace(/^https:\/\//, ''),

/**
* Remove HTML tags from a string.
* @function
* @param {string} str - The string containing HTML tags.
* @returns {string} - The string with HTML tags removed.
*/
stripHTML: str =>
str
.replace(/<\/p>/, '\n')
Expand All @@ -55,56 +175,118 @@ const hbs = create({
}
});

app.set('domain', DOMAIN);
app.set('port', process.env.PORT || PORT || 3000);
app.set('port-https', process.env.PORT_HTTPS || 8443);
app.engine('handlebars', hbs.engine);
app.set('views', PATH_TO_TEMPLATES);
app.set('view engine', 'handlebars');
app.use(
bodyParser.json({
type: 'application/activity+json'
})
); // support json encoded bodies
app.use(
bodyParser.json({
type: 'application/json'
})
); // support json encoded bodies
app.use(
bodyParser.json({
type: 'application/ld+json'
})
); // support json encoded bodies

app.use(cookieParser());

app.use(
bodyParser.urlencoded({
extended: true
})
); // support encoded bodies

// basic http authorizer
const basicUserAuth = basicAuth({
authorizer: asyncAuthorizer,
authorizeAsync: true,
challenge: true
});
const setExpressApp = app => {
app.set('domain', DOMAIN);
app.set('port', process.env.PORT || PORT || 3000);
app.set('port-https', process.env.PORT_HTTPS || 8443);
app.engine('handlebars', hbs.engine);
app.set('views', PATH_TO_TEMPLATES);
app.set('view engine', 'handlebars');
app.use(
bodyParser.json({
type: 'application/activity+json'
})
); // support json encoded bodies
app.use(
bodyParser.json({
type: 'application/json'
})
); // support json encoded bodies
app.use(
bodyParser.json({
type: 'application/ld+json'
})
); // support json encoded bodies

app.use(cookieParser());

function asyncAuthorizer(username, password, cb) {
app.use(
bodyParser.urlencoded({
extended: true
})
); // support encoded bodies
};

setExpressApp(app);

/**
* Asynchronous basic authorization function for Express.js.
*
* @param {string} username - The provided username for authorization.
* @param {string} password - The provided password for authorization.
* @param {Function} callback - The callback function to be called upon authorization completion.
* @param {Error} callback.error - An error object if an error occurred during authorization, or null if successful.
* @param {boolean} callback.authorized - A boolean indicating whether the user is authorized.
*
* @example
* // Example usage:
* asyncAuthorizer('admin', 'password123', (error, authorized) => {
* if (error) {
* console.error(error.message);
* } else {
* console.log(`User is authorized: ${authorized}`);
* }
* });
*/
const asyncAuthorizer = (username, password, callback) => {
let isAuthorized = false;
// Check if the provided password matches the hardcoded username
const isPasswordAuthorized = username === USERNAME;

// Check if the provided username matches the hardcoded password
const isUsernameAuthorized = password === PASS;

// Set isAuthorized to true if both username and password are authorized
isAuthorized = isPasswordAuthorized && isUsernameAuthorized;

// Invoke the callback with the authorization result
if (isAuthorized) {
return cb(null, true);
return callback(null, true);
} else {
return cb(null, false);
return callback(null, false);
}
}
};

/**
* Express.js middleware for basic user authentication using asyncAuthorizer.
*
* @typedef {Object} BasicUserAuth
* @property {Function} authorize - Function to perform basic authorization using asyncAuthorizer.
* @property {boolean} authorizeAsync - Indicates that authorization is performed asynchronously.
* @property {boolean} challenge - Indicates whether to send a 401 Unauthorized response.
*
* @example
* // Example usage:
* app.use(basicUserAuth);
*/
const basicUserAuth = basicAuth({
/**
* Function to perform basic authorization using asyncAuthorizer.
*
* @function
* @param {string} username - The provided username for authorization.
* @param {string} password - The provided password for authorization.
* @param {Function} callback - The callback function to be called upon authorization completion.
* @param {Error} callback.error - An error object if an error occurred during authorization, or null if successful.
* @param {boolean} callback.authorized - A boolean indicating whether the user is authorized.
*/
authorizer: asyncAuthorizer,

/**
* Indicates that authorization is performed asynchronously.
*
* @type {boolean}
*/
authorizeAsync: true,

/**
* Indicates whether to send a 401 Unauthorized response.
*
* @type {boolean}
*/
challenge: true
});

// Load/create account file
ensureAccount(USERNAME, DOMAIN).then(myaccount => {
const authWrapper = (req, res, next) => {
if (req.cookies.token) {
Expand All @@ -125,9 +307,9 @@ ensureAccount(USERNAME, DOMAIN).then(myaccount => {
app.set('account', myaccount);

// serve webfinger response
app.use('/.well-known/webfinger', cors(), webfinger);
app.use('/.well-known/webfinger', cors(), WebfingerRouter);
// server user profile and follower list
app.use('/u', cors(), account);
app.use('/u', cors(), UserProfileRouter);

// serve individual posts
app.use('/m', cors(), notes);
Expand Down
Loading