forked from RubenVerborgh/NSS2CSS
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a642b28
commit 79f2d76
Showing
2 changed files
with
145 additions
and
3 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,12 @@ | ||
#!/usr/bin/env node | ||
#!/usr/bin/env node --no-warnings | ||
import assert from 'node:assert'; | ||
import { resolve } from 'node:path'; | ||
import { opendir, readFile } from 'node:fs/promises'; | ||
|
||
const expectedArgs = { | ||
'nss/config.json': 'path to the NSS configuration file', | ||
'https://css.pod/': 'URL to the running CSS server instance', | ||
'[email protected]': 'e-mail pattern to generate CSS usernames', | ||
}; | ||
|
||
main(...process.argv).catch(console.error); | ||
|
@@ -23,11 +25,14 @@ async function main(bin, script, ...args) { | |
|
||
// Copies the pods and accounts from NSS disk storage | ||
// to a CSS instance and associated disk storage | ||
async function copyNssPodsToCSS(nssConfigPath) { | ||
async function copyNssPodsToCSS(nssConfigPath, cssUrl, emailPattern) { | ||
print('1️⃣ NSS: Read pod configurations from disk'); | ||
const nss = await readNssConfig(nssConfigPath); | ||
const pods = await readPodConfigs(nss.dbPath); | ||
print(`Found ${pods.length} pods`); | ||
|
||
print(`2️⃣ CSS: Create ${pods.length} accounts with pods via HTTP`); | ||
const accounts = await createAccounts(pods, cssUrl, emailPattern); | ||
print(`Created ${Object.keys(accounts).length} accounts`); | ||
} | ||
|
||
// Reads the configuration of an NSS instance | ||
|
@@ -68,6 +73,102 @@ async function readPodConfig(configPath) { | |
return pod; | ||
} | ||
|
||
// Creates a CSS account and pod for each of the NSS pods | ||
async function createAccounts(pods, cssUrl, emailPattern) { | ||
const accounts = {}; | ||
const emailDomain = emailPattern.replace(/.*@+/, ''); | ||
const { account: { create } } = await getAccountControls(cssUrl); | ||
for (const pod of pods) { | ||
try { | ||
const account = await createAccount(create, pod.username, emailDomain); | ||
accounts[account.id] = { ...pod, ...account }; | ||
} | ||
catch { /* Skip unsuccessful accounts */ } | ||
} | ||
return accounts; | ||
} | ||
|
||
// Creates a CSS account with a login and pod | ||
async function createAccount(creationUrl, name, emailDomain) { | ||
const checks = { account: false, login: false, pod: false }; | ||
|
||
try { | ||
// Create and obtain a new empty account | ||
const { resource, cookie } = await cssPost(creationUrl); | ||
const { controls } = await cssGet(resource, cookie); | ||
const [, id] = /account\/([^/]+)\/$/.exec(resource); | ||
checks.account = true; | ||
|
||
// Create a login to the account with a temporary password | ||
const password = generateRandomPassword(); | ||
// We have to generate a new e-mail address per pod, | ||
// since NSS does not perform e-mail validation on sign-up. | ||
// As such, there exists a security risk in which | ||
// an attacker registers a bogus pod with someone else's e-mail, | ||
// in an attempt to gain access to all pods under that e-mail. | ||
const email = `${name}@${emailDomain}`; | ||
await cssPost(controls.password.create, { email, password }, cookie); | ||
checks.login = true; | ||
|
||
// Create a pod under the account | ||
await cssPost(controls.account.pod, { name }, cookie); | ||
checks.pod = true; | ||
|
||
return { id, email, cookie, controls }; | ||
} | ||
finally { | ||
assert(printChecks(name, checks), 'Could not create account'); | ||
} | ||
} | ||
|
||
// Retrieves the CSS hypermedia controls for the account API | ||
async function getAccountControls(cssUrl) { | ||
try { | ||
const body = await cssGet(new URL('.account/', cssUrl)); | ||
assert.equal(body.version, '0.5', 'Unsupported CSS account API'); | ||
return body.controls; | ||
} | ||
catch (cause) { | ||
throw new Error(`Could not access CSS configuration at ${cssUrl}`, { cause }); | ||
} | ||
} | ||
|
||
// Retrieves JSON from CSS via an authenticated HTTP request | ||
async function cssGet(url, cookie = '') { | ||
return cssFetch(url, {}, cookie); | ||
} | ||
|
||
// Posts JSON to CSS via an authenticated HTTP request | ||
async function cssPost(url, body = {}, cookie = '') { | ||
return cssFetch(url, { | ||
method: 'POST', | ||
headers: { 'content-type': 'application/json' }, | ||
body: JSON.stringify(body), | ||
}, cookie); | ||
} | ||
|
||
// Performs an authenticated HTTP request on CSS | ||
async function cssFetch(url, options = {}, cookie = '') { | ||
const response = await fetch(url, { | ||
...options, | ||
headers: { | ||
...options.headers, | ||
accept: 'application/json', | ||
cookie: `css-account=${cookie}`, | ||
}, | ||
}); | ||
const json = await response.json(); | ||
if (response.status !== 200) | ||
throw new Error(json.message); | ||
return json; | ||
} | ||
|
||
// Generates a random password | ||
function generateRandomPassword(length = 32) { | ||
return new Array(length).fill(0).map(() => | ||
String.fromCharCode(65 + Math.floor(58 * Math.random()))).join(''); | ||
} | ||
|
||
// Prints a message to the console | ||
function print(message) { | ||
process.stdout.write(`${message}\n`); | ||
|
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,41 @@ | ||
{ | ||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld", | ||
"import": [ | ||
"css:config/app/main/default.json", | ||
"css:config/app/init/static-root.json", | ||
"css:config/app/variables/default.json", | ||
"css:config/http/handler/default.json", | ||
"css:config/http/middleware/default.json", | ||
"css:config/http/notifications/websockets.json", | ||
"css:config/http/server-factory/http.json", | ||
"css:config/http/static/default.json", | ||
"css:config/identity/access/public.json", | ||
"css:config/identity/email/default.json", | ||
"css:config/identity/handler/default.json", | ||
"css:config/identity/interaction/default.json", | ||
"css:config/identity/ownership/token.json", | ||
"css:config/identity/pod/static.json", | ||
"css:config/ldp/authentication/dpop-bearer.json", | ||
"css:config/ldp/authorization/webacl.json", | ||
"css:config/ldp/handler/default.json", | ||
"css:config/ldp/metadata-parser/default.json", | ||
"css:config/ldp/metadata-writer/default.json", | ||
"css:config/ldp/modes/default.json", | ||
"css:config/storage/backend/file.json", | ||
"css:config/storage/key-value/resource-store.json", | ||
"css:config/storage/location/pod.json", | ||
"css:config/storage/middleware/default.json", | ||
"css:config/util/auxiliary/acl.json", | ||
"css:config/util/identifiers/subdomain.json", | ||
"css:config/util/index/default.json", | ||
"css:config/util/logging/winston.json", | ||
"css:config/util/representation-conversion/default.json", | ||
"css:config/util/resource-locker/file.json", | ||
"css:config/util/variables/default.json" | ||
], | ||
"@graph": [ | ||
{ | ||
"comment": "A Solid server that stores its resources on disk, uses WAC for authorization, and subdomains for pods." | ||
} | ||
] | ||
} |