-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
modular server: Adds signup route & implements basePath config
- Loading branch information
1 parent
3849941
commit 9a3ea4c
Showing
18 changed files
with
962 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
const express = require('express'); | ||
const router = express.Router(); | ||
const getHost = require('../util/getHost'); | ||
const errorPage = require('../util/errorPage'); | ||
const { getLogger } = require('../logger'); | ||
|
||
const DISABLED_LOCALS = { title: 'Forbidden', message: 'Signing up is not allowed currently' }; | ||
const DISABLED_LOG_NOTE = 'signups disabled'; | ||
|
||
/* initial entry */ | ||
router.get('/', function (req, res) { | ||
if (req.app?.locals?.signup) { | ||
res.render('signup.html', { | ||
title: 'Signup', | ||
params: {}, | ||
error: null, | ||
host: getHost(req) | ||
}); | ||
} else { | ||
errorPage(req, res, 403, DISABLED_LOCALS, DISABLED_LOG_NOTE); | ||
} | ||
}); | ||
|
||
/* submission or re-submission */ | ||
router.post('/', | ||
express.urlencoded({ extended: false }), | ||
async function (req, res) { | ||
if (req.app?.locals?.signup) { | ||
try { | ||
const store = req.app?.get('streaming store'); | ||
const bucketName = await store.createUser(req.body); | ||
getLogger().notice(`created bucket “${bucketName}” for user “${req.body.username}”`); | ||
res.status(201).render('signup-success.html', { | ||
title: 'Signup Success', | ||
params: req.body, | ||
host: getHost(req) | ||
}); | ||
} catch (err) { | ||
getLogger().error(`while creating user “${req.body?.username}”`, err); | ||
res.status(409).render('signup.html', { | ||
title: 'Signup Failure', | ||
params: req.body, | ||
error: err, | ||
host: getHost(req) | ||
}); | ||
} | ||
} else { | ||
errorPage(req, res, 403, DISABLED_LOCALS, DISABLED_LOG_NOTE); | ||
} | ||
}); | ||
|
||
module.exports = router; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
const Minio = require('minio'); | ||
const core = require('../stores/core'); | ||
const { getLogger } = require('../logger'); | ||
|
||
// const FILE_PREFIX = 'remoteStorageBlob/'; | ||
// const AUTH_PREFIX = 'remoteStorageAuth/'; | ||
|
||
/** uses the min.io client to connect to any S3-compatible storage that supports versioning */ | ||
class S3 { | ||
#minioClient; | ||
|
||
/** Using the default arguments connects you to a public server where anyone can read and delete your data! */ | ||
constructor (endPoint = 'play.min.io', port = 9000, accessKey = 'Q3AM3UQ867SPQQA43P2F', secretKey = 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG') { | ||
this.#minioClient = new Minio.Client({ | ||
endPoint, | ||
port, | ||
accessKey, | ||
secretKey, | ||
useSSL: !['localhost', '10.0.0.2', '127.0.0.1'].includes(endPoint) | ||
}); | ||
} | ||
|
||
/** | ||
* Creates an empty bucket for the new user. | ||
* @param {Object} params | ||
* @param {string} params.username | ||
* @param {string} params.email | ||
* @param {string} params.password | ||
* @returns {Promise<string>} name of bucket | ||
*/ | ||
async createUser (params) { | ||
const errors = core.validateUser(params); | ||
if (errors.length > 0) { | ||
const msg = errors.map(err => err.message).join('|'); | ||
throw new Error(msg); | ||
} | ||
|
||
const bucketName = params.username; | ||
const exists = await this.#minioClient.bucketExists(bucketName); | ||
if (exists) { | ||
throw new Error(`Username “${params.username}” is already taken`); | ||
} else { | ||
await this.#minioClient.makeBucket(bucketName); | ||
await this.#minioClient.setBucketVersioning(bucketName, | ||
{ Status: 'Enabled', ExcludedPrefixes: [{ Prefix: 'permissions' }, { Prefix: 'meta' }] }); | ||
getLogger().info(`bucket ${bucketName} created.`); | ||
return bucketName; | ||
} | ||
} | ||
|
||
/** | ||
* Deletes all of user's files and the bucket. NOT REVERSIBLE. | ||
* @param username | ||
* @returns {Promise<number>} number of files deleted | ||
*/ | ||
async deleteUser (username) { | ||
if (!await this.#minioClient.bucketExists(username)) { return 0; } | ||
|
||
return new Promise((resolve, reject) => { | ||
const GROUP_SIZE = 100; | ||
const objectVersions = []; | ||
let numRequested = 0; let numRemoved = 0; let isReceiveComplete = false; | ||
|
||
const removeObjectVersions = async () => { | ||
const group = objectVersions.slice(0); | ||
objectVersions.length = 0; | ||
numRequested += group.length; | ||
await this.#minioClient.removeObjects(username, group); | ||
numRemoved += group.length; | ||
|
||
if (isReceiveComplete && numRemoved === numRequested) { | ||
await this.#minioClient.removeBucket(username); // will fail if any object versions remain | ||
resolve(numRemoved); | ||
} | ||
}; | ||
|
||
const removeObjectVersionsAndBucket = async err => { | ||
try { | ||
isReceiveComplete = true; | ||
await removeObjectVersions(); | ||
if (err) { | ||
reject(err); | ||
} | ||
} catch (err2) { | ||
reject(err || err2); | ||
} | ||
}; | ||
|
||
const objectVersionStream = this.#minioClient.listObjects(username, '', true, { IncludeVersion: true }); | ||
objectVersionStream.on('data', async item => { | ||
try { | ||
objectVersions.push(item); | ||
if (objectVersions.length >= GROUP_SIZE) { | ||
await removeObjectVersions(); | ||
} | ||
} catch (err) { // keeps going | ||
getLogger().error(`while deleting user “${username}” object version ${JSON.stringify(item)}:`, err); | ||
} | ||
}); | ||
objectVersionStream.on('error', removeObjectVersionsAndBucket); | ||
objectVersionStream.on('end', removeObjectVersionsAndBucket); | ||
}); | ||
} | ||
} | ||
|
||
module.exports = S3; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# S3-compatible Streaming Store | ||
|
||
Streaming Stores can only be used with the modular server. | ||
|
||
You should be able to connect to any S3-compatible service that supports versioning. Tested services include: | ||
|
||
* min.io (both self-hosted and cloud) | ||
|
||
|
||
Configure the store by passing to the constructor the endpoint (host name), access key (admin user name) and secret key (password). For non-Amazon providers, you may need to pass in a port number as well. You can provide these however you like, but typically they are stored in these environment variables: | ||
|
||
* S3_HOSTNAME | ||
* S3_PORT | ||
* S3_ACCESS_KEY | ||
* S3_SECRET_KEY | ||
|
||
Creating a client then resembles: | ||
|
||
```javascript | ||
const store = new S3(process.env.S3_HOSTNAME, | ||
process.env.S3_PORT ? parseInt(process.env.S3_PORT) : undefined, | ||
process.env.S3_ACCESS_KEY, process.env.S3_SECRET_KEY); | ||
``` | ||
|
||
This one access key is used to create a bucket for each user. | ||
The bucket name is the username. | ||
Buckets can be administered using the service's tools, such as a webapp console or command-line tools. | ||
The bucket can contain non-remoteStorage blobs outside these prefixes: | ||
|
||
* remoteStorageBlob/ | ||
* remoteStorageAuth/ |
Oops, something went wrong.