format project

This commit is contained in:
counterweight 2025-02-14 11:13:18 +01:00
parent 90d8e39eb3
commit c02cf8c12e
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
39 changed files with 2062 additions and 909 deletions

19
.eslintrc.json Normal file
View file

@ -0,0 +1,19 @@
{
"env": {
"browser": true,
"node": true,
"es6": true
},
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
"rules": {
"prettier/prettier": [
"error",
{
"singleQuote": true,
"semi": false,
"trailingComma": "es5"
}
],
"no-console": "warn"
}
}

8
.prettierrc Normal file
View file

@ -0,0 +1,8 @@
{
"plugins": [
"prettier-plugin-ejs"
],
"singleQuote": true,
"semi": true,
"trailingComma": "es5"
}

9
eslint.config.mjs Normal file
View file

@ -0,0 +1,9 @@
import globals from 'globals'
import pluginJs from '@eslint/js'
/** @type {import('eslint').Linter.Config[]} */
export default [
{ files: ['**/*.js'], languageOptions: { sourceType: 'commonjs' } },
{ languageOptions: { globals: { ...globals.browser, ...globals.node } } },
pluginJs.configs.recommended,
]

1041
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -20,9 +20,20 @@
"start:containers": "docker compose up -d --build", "start:containers": "docker compose up -d --build",
"stop:containers": "docker compose down", "stop:containers": "docker compose down",
"cli": "node src/cli.js", "cli": "node src/cli.js",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1",
"lint": "eslint . --fix",
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,scss,html,ejs}\""
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
"license": "ISC" "license": "ISC",
"devDependencies": {
"@eslint/js": "^9.20.0",
"eslint": "^9.20.1",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.3",
"globals": "^15.15.0",
"prettier": "^3.5.1",
"prettier-plugin-ejs": "^1.0.3"
}
} }

View file

@ -3,9 +3,7 @@ const program = new Command();
const createAppInviteCommand = require('./commands/createAppInvite'); const createAppInviteCommand = require('./commands/createAppInvite');
program program.version('1.0.0').description('CLI for managing web app tasks');
.version('1.0.0')
.description('CLI for managing web app tasks');
program program
.command('createAppInvite <inviterNpub>') .command('createAppInvite <inviterNpub>')

View file

@ -3,5 +3,5 @@ const DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS = 60 * 60 * 24 * 30;
module.exports = { module.exports = {
DEFAULT_SESSION_DURATION_SECONDS, DEFAULT_SESSION_DURATION_SECONDS,
DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS,
} };

View file

@ -14,14 +14,17 @@ const sequelize = new Sequelize({
timestamps: false, timestamps: false,
freezeTableName: true, freezeTableName: true,
underscored: true, underscored: true,
quoteIdentifiers: false quoteIdentifiers: false,
}, },
}); });
sequelize.sync().then(() => { sequelize
.sync()
.then(() => {
console.log('Database synced'); console.log('Database synced');
}).catch(err => { })
.catch((err) => {
console.error('Error syncing the database:', err); console.error('Error syncing the database:', err);
}); });
module.exports = sequelize; module.exports = sequelize;

View file

@ -1,21 +1,21 @@
class AlreadyUsedError extends Error { class AlreadyUsedError extends Error {
constructor(message) { constructor(message) {
super(message); super(message);
this.name = "AlreadyUsedError" this.name = 'AlreadyUsedError';
} }
} }
class InvalidSignatureError extends Error { class InvalidSignatureError extends Error {
constructor(message) { constructor(message) {
super(message); super(message);
this.name = "InvalidSignatureError"; this.name = 'InvalidSignatureError';
} }
} }
class NotFoundError extends Error { class NotFoundError extends Error {
constructor(message) { constructor(message) {
super(message); super(message);
this.name = "AppInvitedUsedError"; this.name = 'AppInvitedUsedError';
} }
} }
@ -30,5 +30,5 @@ module.exports = {
AlreadyUsedError, AlreadyUsedError,
InvalidSignatureError, InvalidSignatureError,
NotFoundError, NotFoundError,
ExpiredError ExpiredError,
}; };

View file

@ -1,10 +1,9 @@
const sessionService = require('../services/sessionService'); const sessionService = require('../services/sessionService');
async function attachPublicKeyMiddleware(req, res, next) { async function attachPublicKeyMiddleware(req, res, next) {
const publicKey = await sessionService.getPublicKeyRelatedToSession( const publicKey = await sessionService.getPublicKeyRelatedToSession(
req.cookies.sessionUuid req.cookies.sessionUuid
) );
req.cookies.publicKey = publicKey; req.cookies.publicKey = publicKey;
next(); next();

View file

@ -4,8 +4,8 @@ async function rejectIfNotAuthorizedMiddleware(req, res, next) {
if (!(await sessionService.isSessionAuthorized(req.cookies.sessionUuid))) { if (!(await sessionService.isSessionAuthorized(req.cookies.sessionUuid))) {
return res.status(403).json({ return res.status(403).json({
success: false, success: false,
message: 'Your session is not authorized.' message: 'Your session is not authorized.',
}) });
} }
next(); next();
} }

View file

@ -1,22 +1,23 @@
const uuid = require("uuid"); const uuid = require('uuid');
const sessionService = require('../services/sessionService'); const sessionService = require('../services/sessionService');
const constants = require('../constants'); const constants = require('../constants');
async function setAndPersistNewSession(res) { async function setAndPersistNewSession(res) {
const sessionUuid = uuid.v7(); const sessionUuid = uuid.v7();
res.cookie('sessionUuid', sessionUuid, { httpOnly: true, maxAge: constants.DEFAULT_SESSION_DURATION_SECONDS * 1000 }); res.cookie('sessionUuid', sessionUuid, {
httpOnly: true,
maxAge: constants.DEFAULT_SESSION_DURATION_SECONDS * 1000,
});
return await sessionService.createSession(sessionUuid); return await sessionService.createSession(sessionUuid);
} }
async function createSessionMiddleware(req, res, next) { async function createSessionMiddleware(req, res, next) {
const sessionUuid = req.cookies.sessionUuid; const sessionUuid = req.cookies.sessionUuid;
if (!sessionUuid) { if (!sessionUuid) {
const newSession = await setAndPersistNewSession(res); const newSession = await setAndPersistNewSession(res);
req.cookies.sessionUuid = newSession.uuid; req.cookies.sessionUuid = newSession.uuid;
} }
if (sessionUuid) { if (sessionUuid) {

View file

@ -1,12 +1,14 @@
const { DataTypes } = require('sequelize'); const { DataTypes } = require('sequelize');
const sequelize = require('../database'); const sequelize = require('../database');
const AppInviteCreated = sequelize.define('AppInviteCreated', { const AppInviteCreated = sequelize.define(
'AppInviteCreated',
{
uuid: { uuid: {
type: DataTypes.UUID, type: DataTypes.UUID,
allowNull: false, allowNull: false,
unique: true, unique: true,
primaryKey: true primaryKey: true,
}, },
inviter_pub_key: { inviter_pub_key: {
type: DataTypes.STRING, type: DataTypes.STRING,
@ -14,10 +16,12 @@ const AppInviteCreated = sequelize.define('AppInviteCreated', {
}, },
created_at: { created_at: {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: false allowNull: false,
},
},
{
tableName: 'app_invite_created',
} }
}, { );
tableName: 'app_invite_created'
});
module.exports = AppInviteCreated; module.exports = AppInviteCreated;

View file

@ -1,27 +1,31 @@
const { DataTypes } = require('sequelize'); const { DataTypes } = require('sequelize');
const sequelize = require('../database'); const sequelize = require('../database');
const ContactDetailsSet = sequelize.define('ContactDetailsSet', { const ContactDetailsSet = sequelize.define(
'ContactDetailsSet',
{
uuid: { uuid: {
type: DataTypes.UUID, type: DataTypes.UUID,
allowNull: false, allowNull: false,
unique: true, unique: true,
primaryKey: true primaryKey: true,
}, },
public_key: { public_key: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false allowNull: false,
}, },
encrypted_contact_details: { encrypted_contact_details: {
type: DataTypes.TEXT, type: DataTypes.TEXT,
allowNull: false allowNull: false,
}, },
created_at: { created_at: {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: false allowNull: false,
},
},
{
tableName: 'contact_details_set',
} }
}, { );
tableName: 'contact_details_set'
});
module.exports = ContactDetailsSet; module.exports = ContactDetailsSet;

View file

@ -1,31 +1,35 @@
const { DataTypes } = require('sequelize'); const { DataTypes } = require('sequelize');
const sequelize = require('../database'); const sequelize = require('../database');
const NostrChallengeCompleted = sequelize.define('NostrChallengeCompleted', { const NostrChallengeCompleted = sequelize.define(
'NostrChallengeCompleted',
{
uuid: { uuid: {
type: DataTypes.UUID, type: DataTypes.UUID,
allowNull: false, allowNull: false,
unique: true, unique: true,
primaryKey: true primaryKey: true,
}, },
challenge: { challenge: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false allowNull: false,
}, },
signed_event: { signed_event: {
type: DataTypes.JSONB, type: DataTypes.JSONB,
allowNull: false allowNull: false,
}, },
public_key: { public_key: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false allowNull: false,
}, },
created_at: { created_at: {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: false allowNull: false,
},
},
{
tableName: 'nostr_challenge_completed',
} }
}, { );
tableName: 'nostr_challenge_completed'
});
module.exports = NostrChallengeCompleted; module.exports = NostrChallengeCompleted;

View file

@ -1,27 +1,31 @@
const { DataTypes } = require('sequelize'); const { DataTypes } = require('sequelize');
const sequelize = require('../database'); const sequelize = require('../database');
const NostrChallengeCreated = sequelize.define('NostrChallengeCreated', { const NostrChallengeCreated = sequelize.define(
'NostrChallengeCreated',
{
uuid: { uuid: {
type: DataTypes.UUID, type: DataTypes.UUID,
allowNull: false, allowNull: false,
unique: true, unique: true,
primaryKey: true primaryKey: true,
}, },
challenge: { challenge: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false allowNull: false,
}, },
expires_at: { expires_at: {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: false allowNull: false,
}, },
created_at: { created_at: {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: false allowNull: false,
},
},
{
tableName: 'nostr_challenge_created',
} }
}, { );
tableName: 'nostr_challenge_created'
});
module.exports = NostrChallengeCreated; module.exports = NostrChallengeCreated;

View file

@ -1,27 +1,31 @@
const { DataTypes } = require('sequelize'); const { DataTypes } = require('sequelize');
const sequelize = require('../database'); const sequelize = require('../database');
const NymSet = sequelize.define('NymSet', { const NymSet = sequelize.define(
'NymSet',
{
uuid: { uuid: {
type: DataTypes.UUID, type: DataTypes.UUID,
allowNull: false, allowNull: false,
unique: true, unique: true,
primaryKey: true primaryKey: true,
}, },
public_key: { public_key: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false allowNull: false,
}, },
nym: { nym: {
type: DataTypes.TEXT, type: DataTypes.TEXT,
allowNull: false allowNull: false,
}, },
created_at: { created_at: {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: false allowNull: false,
},
},
{
tableName: 'nym_set',
} }
}, { );
tableName: 'nym_set'
});
module.exports = NymSet; module.exports = NymSet;

View file

@ -1,23 +1,27 @@
const { DataTypes } = require('sequelize'); const { DataTypes } = require('sequelize');
const sequelize = require('../database'); const sequelize = require('../database');
const SessionCreated = sequelize.define('SessionCreated', { const SessionCreated = sequelize.define(
'SessionCreated',
{
uuid: { uuid: {
type: DataTypes.UUID, type: DataTypes.UUID,
allowNull: false, allowNull: false,
unique: true, unique: true,
primaryKey: true primaryKey: true,
}, },
created_at: { created_at: {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: false allowNull: false,
}, },
expires_at: { expires_at: {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: false allowNull: false,
},
},
{
tableName: 'session_created',
} }
}, { );
tableName: 'session_created'
});
module.exports = SessionCreated; module.exports = SessionCreated;

View file

@ -1,12 +1,14 @@
const { DataTypes } = require('sequelize'); const { DataTypes } = require('sequelize');
const sequelize = require('../database'); const sequelize = require('../database');
const SessionRelatedToPublickey = sequelize.define('SessionRelatedToPublickey', { const SessionRelatedToPublickey = sequelize.define(
'SessionRelatedToPublickey',
{
uuid: { uuid: {
type: DataTypes.UUID, type: DataTypes.UUID,
allowNull: false, allowNull: false,
unique: true, unique: true,
primaryKey: true primaryKey: true,
}, },
session_uuid: { session_uuid: {
type: DataTypes.UUID, type: DataTypes.UUID,
@ -14,14 +16,16 @@ const SessionRelatedToPublickey = sequelize.define('SessionRelatedToPublickey',
}, },
public_key: { public_key: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false allowNull: false,
}, },
created_at: { created_at: {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: false allowNull: false,
},
},
{
tableName: 'session_related_to_public_key',
} }
}, { );
tableName: 'session_related_to_public_key'
});
module.exports = SessionRelatedToPublickey; module.exports = SessionRelatedToPublickey;

View file

@ -1,12 +1,14 @@
const { DataTypes } = require('sequelize'); const { DataTypes } = require('sequelize');
const sequelize = require('../database'); const sequelize = require('../database');
const SignUpChallengeCompleted = sequelize.define('SignUpChallengeCompleted', { const SignUpChallengeCompleted = sequelize.define(
'SignUpChallengeCompleted',
{
uuid: { uuid: {
type: DataTypes.UUID, type: DataTypes.UUID,
allowNull: false, allowNull: false,
unique: true, unique: true,
primaryKey: true primaryKey: true,
}, },
nostr_challenge_completed_uuid: { nostr_challenge_completed_uuid: {
type: DataTypes.UUID, type: DataTypes.UUID,
@ -14,18 +16,20 @@ const SignUpChallengeCompleted = sequelize.define('SignUpChallengeCompleted', {
}, },
app_invite_uuid: { app_invite_uuid: {
type: DataTypes.UUID, type: DataTypes.UUID,
allowNull: false allowNull: false,
}, },
public_key: { public_key: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false allowNull: false,
}, },
created_at: { created_at: {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: false allowNull: false,
},
},
{
tableName: 'sign_up_challenge_completed',
} }
}, { );
tableName: 'sign_up_challenge_completed'
});
module.exports = SignUpChallengeCompleted; module.exports = SignUpChallengeCompleted;

View file

@ -1,27 +1,31 @@
const { DataTypes } = require('sequelize'); const { DataTypes } = require('sequelize');
const sequelize = require('../database'); const sequelize = require('../database');
const SignUpChallengeCreated = sequelize.define('SignUpChallengeCreated', { const SignUpChallengeCreated = sequelize.define(
'SignUpChallengeCreated',
{
uuid: { uuid: {
type: DataTypes.UUID, type: DataTypes.UUID,
allowNull: false, allowNull: false,
unique: true, unique: true,
primaryKey: true primaryKey: true,
}, },
nostr_challenge_uuid: { nostr_challenge_uuid: {
type: DataTypes.UUID, type: DataTypes.UUID,
allowNull: false allowNull: false,
}, },
app_invite_uuid: { app_invite_uuid: {
type: DataTypes.UUID, type: DataTypes.UUID,
allowNull: false allowNull: false,
}, },
created_at: { created_at: {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: false allowNull: false,
},
},
{
tableName: 'sign_up_challenge_created',
} }
}, { );
tableName: 'sign_up_challenge_created'
});
module.exports = SignUpChallengeCreated; module.exports = SignUpChallengeCreated;

View file

@ -9,7 +9,9 @@ class ContactDetails {
} }
removeDetails(type, value) { removeDetails(type, value) {
this.details = this.details.filter(detail => detail.type !== type || detail.value !== value); this.details = this.details.filter(
(detail) => detail.type !== type || detail.value !== value
);
} }
syncUi() { syncUi() {
@ -19,20 +21,20 @@ class ContactDetails {
const addedDetailFragment = this.buildContactDetailBadge(detail); const addedDetailFragment = this.buildContactDetailBadge(detail);
this.rootUiElement.appendChild(addedDetailFragment); this.rootUiElement.appendChild(addedDetailFragment);
}); });
}) });
} }
buildContactDetailBadge(detail) { buildContactDetailBadge(detail) {
const fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();
const div = document.createElement("div"); const div = document.createElement('div');
div.className = "added-contact-detail badge"; div.className = 'added-contact-detail badge';
const p = document.createElement("p"); const p = document.createElement('p');
p.textContent = `${detail.type}: ${detail.value}`; p.textContent = `${detail.type}: ${detail.value}`;
const button = document.createElement("button"); const button = document.createElement('button');
button.textContent = "Eliminar"; button.textContent = 'Eliminar';
button.onclick = () => { button.onclick = () => {
this.removeDetails(detail.type, detail.value); this.removeDetails(detail.type, detail.value);
this.syncUi(); this.syncUi();
@ -48,7 +50,10 @@ class ContactDetails {
async getEncryptedContactDetails() { async getEncryptedContactDetails() {
const jsonString = JSON.stringify(this.details); const jsonString = JSON.stringify(this.details);
const encryptedContactDetails = await window.nostr.nip04.encrypt(await window.nostr.getPublicKey(), jsonString); const encryptedContactDetails = await window.nostr.nip04.encrypt(
await window.nostr.getPublicKey(),
jsonString
);
return encryptedContactDetails; return encryptedContactDetails;
} }
} }
@ -56,9 +61,11 @@ class ContactDetails {
let contactDetails; let contactDetails;
window.onload = () => { window.onload = () => {
contactDetails = new ContactDetails(document.querySelector('#created-contact-details-list')); contactDetails = new ContactDetails(
document.querySelector('#created-contact-details-list')
);
document.querySelectorAll('.contact-detail-add-button').forEach(button => { document.querySelectorAll('.contact-detail-add-button').forEach((button) => {
button.addEventListener('click', function () { button.addEventListener('click', function () {
const badge = this.parentElement; const badge = this.parentElement;
const type = badge.getAttribute('data-type'); const type = badge.getAttribute('data-type');
@ -76,29 +83,24 @@ window.onload = () => {
document document
.querySelector('#submit-details-button') .querySelector('#submit-details-button')
.addEventListener( .addEventListener('click', async () => {
'click', const encryptedContactDetails =
async () => { await contactDetails.getEncryptedContactDetails();
const encryptedContactDetails = await contactDetails.getEncryptedContactDetails(); await fetch('/api/set-contact-details', {
await fetch('/api/set-contact-details',
{
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ encryptedContactDetails }) body: JSON.stringify({ encryptedContactDetails }),
}); });
const nym = document.querySelector('#nym-input').value; const nym = document.querySelector('#nym-input').value;
await fetch('/api/set-nym', await fetch('/api/set-nym', {
{
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ nym }) body: JSON.stringify({ nym }),
});
}); });
}
);
}; };

View file

@ -1,22 +1,21 @@
window.onload = function () { window.onload = function () {
if (!window.nostr) { if (!window.nostr) {
console.log("Nostr extension not present"); console.log('Nostr extension not present');
document.querySelector('#nostr-signup').disabled = true; document.querySelector('#nostr-signup').disabled = true;
document.querySelector('#no-extension-nudges').style.display = 'block'; document.querySelector('#no-extension-nudges').style.display = 'block';
} else { } else {
console.log("Nostr extension present"); console.log('Nostr extension present');
} }
} };
async function acceptInvite() { async function acceptInvite() {
let challengeResponse; let challengeResponse;
try { try {
challengeResponse = await fetch('/api/signup/nostr-challenge', { challengeResponse = await fetch('/api/signup/nostr-challenge', {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
} },
}); });
} catch (error) { } catch (error) {
console.log(`Something went wrong: ${error}`); console.log(`Something went wrong: ${error}`);
@ -35,9 +34,9 @@ async function acceptInvite() {
const event = { const event = {
kind: 22242, kind: 22242,
created_at: Math.floor(Date.now() / 1000), created_at: Math.floor(Date.now() / 1000),
tags: [["challenge", challenge]], tags: [['challenge', challenge]],
content: "Sign this challenge to authenticate", content: 'Sign this challenge to authenticate',
pubkey: pubkey pubkey: pubkey,
}; };
let signedEvent; let signedEvent;
@ -50,9 +49,9 @@ async function acceptInvite() {
let verifyResponse; let verifyResponse;
try { try {
verifyResponse = await fetch("/api/signup/nostr-verify", { verifyResponse = await fetch('/api/signup/nostr-verify', {
method: "POST", method: 'POST',
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(signedEvent), body: JSON.stringify(signedEvent),
}); });
} catch (error) { } catch (error) {
@ -63,7 +62,7 @@ async function acceptInvite() {
if (verifyResponse.ok) { if (verifyResponse.ok) {
document.querySelector('#sign-up-success').style.display = 'block'; document.querySelector('#sign-up-success').style.display = 'block';
setTimeout(() => { setTimeout(() => {
window.location.href = "/createProfile"; window.location.href = '/createProfile';
}, 1000); }, 1000);
} }
} }

View file

@ -15,84 +15,82 @@ router.get('/signup/nostr-challenge', async (req, res) => {
let signUpChallenge; let signUpChallenge;
try { try {
signUpChallenge = await invitesService.createSignUpChallenge( signUpChallenge = await invitesService.createSignUpChallenge(inviteUuid);
inviteUuid
)
} catch (error) { } catch (error) {
if (error instanceof errors.NotFoundError) { if (error instanceof errors.NotFoundError) {
return res.status(404).json({ return res.status(404).json({
success: false, success: false,
message: 'Could not find invite with that id.' message: 'Could not find invite with that id.',
}) });
} }
if (error instanceof errors.AlreadyUsedError) { if (error instanceof errors.AlreadyUsedError) {
return res.status(410).json({ return res.status(410).json({
success: false, success: false,
message: 'That invite has already been used.' message: 'That invite has already been used.',
}) });
} }
return res.status(500).json({ return res.status(500).json({
success: false, success: false,
message: 'Unexpected error.' message: 'Unexpected error.',
}) });
} }
let relatedNostrChallenge; let relatedNostrChallenge;
try { try {
relatedNostrChallenge = await nostrService.getNostrChallenge( relatedNostrChallenge = await nostrService.getNostrChallenge(
signUpChallenge.nostr_challenge_uuid signUpChallenge.nostr_challenge_uuid
) );
} catch (error) { } catch (error) {
return res.status(500).json({ return res.status(500).json({
success: false, success: false,
message: 'Unexpected error.' message: 'Unexpected error.',
}) });
} }
return res.status(200).json({ 'challenge': relatedNostrChallenge.challenge }); return res.status(200).json({ challenge: relatedNostrChallenge.challenge });
}); });
router.post('/signup/nostr-verify', async (req, res) => {
router.post("/signup/nostr-verify", async (req, res) => {
const signedEvent = req.body; const signedEvent = req.body;
const sessionUuid = req.cookies.sessionUuid; const sessionUuid = req.cookies.sessionUuid;
let completedSignUpChallenge; let completedSignUpChallenge;
try { try {
completedSignUpChallenge = await invitesService.verifySignUpChallenge(signedEvent); completedSignUpChallenge =
await invitesService.verifySignUpChallenge(signedEvent);
} catch (error) { } catch (error) {
if (error instanceof errors.ExpiredError) { if (error instanceof errors.ExpiredError) {
return res.status(410).json({ return res.status(410).json({
success: false, success: false,
message: 'The challenge has expired, request a new one.' message: 'The challenge has expired, request a new one.',
}) });
} }
if (error instanceof errors.AlreadyUsedError) { if (error instanceof errors.AlreadyUsedError) {
return res.status(410).json({ return res.status(410).json({
success: false, success: false,
message: 'The challenge has been used, request a new one.' message: 'The challenge has been used, request a new one.',
}) });
} }
if (error instanceof errors.InvalidSignatureError) { if (error instanceof errors.InvalidSignatureError) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: 'The challenge signature is not valid.' message: 'The challenge signature is not valid.',
}) });
} }
} }
await sessionService.relateSessionToPublicKey( await sessionService.relateSessionToPublicKey(
sessionUuid, sessionUuid,
completedSignUpChallenge.public_key completedSignUpChallenge.public_key
) );
return res.status(200).json({ success: true }); return res.status(200).json({ success: true });
}); });
router.post( router.post(
"/set-contact-details", '/set-contact-details',
rejectIfNotAuthorizedMiddleware, rejectIfNotAuthorizedMiddleware,
attachPublicKeyMiddleware, attachPublicKeyMiddleware,
async (req, res) => { async (req, res) => {
@ -102,24 +100,21 @@ router.post(
if (!encryptedContactDetails) { if (!encryptedContactDetails) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: 'Missing contact details.' message: 'Missing contact details.',
}) });
} }
await profileService.setContactDetails( await profileService.setContactDetails(publicKey, encryptedContactDetails);
publicKey,
encryptedContactDetails
)
return res.status(200).json({ return res.status(200).json({
success: true, success: true,
message: 'Contact details set successfully.' message: 'Contact details set successfully.',
}) });
} }
); );
router.post( router.post(
"/set-nym", '/set-nym',
rejectIfNotAuthorizedMiddleware, rejectIfNotAuthorizedMiddleware,
attachPublicKeyMiddleware, attachPublicKeyMiddleware,
async (req, res) => { async (req, res) => {
@ -129,19 +124,16 @@ router.post(
if (!nym) { if (!nym) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: 'Missing nym' message: 'Missing nym',
}) });
} }
await profileService.setNym( await profileService.setNym(publicKey, nym);
publicKey,
nym
)
return res.status(200).json({ return res.status(200).json({
success: true, success: true,
message: 'Nym set successfully.' message: 'Nym set successfully.',
}) });
} }
); );

View file

@ -2,7 +2,7 @@ const express = require('express');
const router = express.Router(); const router = express.Router();
const redirectIfNotAuthorizedMiddleware = require('../middlewares/redirectIfNotAuthorizedMiddleware'); const redirectIfNotAuthorizedMiddleware = require('../middlewares/redirectIfNotAuthorizedMiddleware');
const invitesService = require('../services/invitesService') const invitesService = require('../services/invitesService');
router.get('/', (req, res) => { router.get('/', (req, res) => {
res.render('index', { uuid: req.cookies.sessionUuid }); res.render('index', { uuid: req.cookies.sessionUuid });
@ -21,9 +21,8 @@ router.get('/invite/:inviteUuid', async (req, res) => {
} }
if (await invitesService.isAppInviteSpent(inviteUuid)) { if (await invitesService.isAppInviteSpent(inviteUuid)) {
return res.status(410).render('invite_spent', { invite }) return res.status(410).render('invite_spent', { invite });
} }
} catch (error) { } catch (error) {
console.error('Error fetching invite:', error); console.error('Error fetching invite:', error);
return res.status(500).render('error', { message: 'An error occurred' }); return res.status(500).render('error', { message: 'An error occurred' });
@ -45,9 +44,8 @@ router.get('/invite/:inviteUuid', async (req, res) => {
} }
if (await invitesService.isAppInviteSpent(inviteUuid)) { if (await invitesService.isAppInviteSpent(inviteUuid)) {
return res.status(410).render('invite_spent', { invite }) return res.status(410).render('invite_spent', { invite });
} }
} catch (error) { } catch (error) {
console.error('Error fetching invite:', error); console.error('Error fetching invite:', error);
return res.status(500).render('error', { message: 'An error occurred' }); return res.status(500).render('error', { message: 'An error occurred' });
@ -56,9 +54,13 @@ router.get('/invite/:inviteUuid', async (req, res) => {
return res.render('invite', { invite }); return res.render('invite', { invite });
}); });
router.get('/createProfile', redirectIfNotAuthorizedMiddleware, async (req, res) => { router.get(
'/createProfile',
redirectIfNotAuthorizedMiddleware,
async (req, res) => {
return res.status(200).render('createProfile'); return res.status(200).render('createProfile');
}) }
);
router.get('/private', redirectIfNotAuthorizedMiddleware, (req, res) => { router.get('/private', redirectIfNotAuthorizedMiddleware, (req, res) => {
res.render('private', {}); res.render('private', {});

View file

@ -8,7 +8,9 @@ const SignUpChallengeCompleted = require('../models/SignUpChallengeCompleted');
const errors = require('../errors'); const errors = require('../errors');
async function appInviteExists(inviteUuid) { async function appInviteExists(inviteUuid) {
const invite = await AppInviteCreated.findOne({ where: { uuid: inviteUuid } }); const invite = await AppInviteCreated.findOne({
where: { uuid: inviteUuid },
});
if (invite) { if (invite) {
return true; return true;
} }
@ -16,16 +18,18 @@ async function appInviteExists(inviteUuid) {
} }
async function getAppInvite(inviteUuid) { async function getAppInvite(inviteUuid) {
const invite = await AppInviteCreated.findOne({ where: { uuid: inviteUuid } }); const invite = await AppInviteCreated.findOne({
where: { uuid: inviteUuid },
});
return invite; return invite;
} }
async function isAppInviteSpent(appInviteUuid) { async function isAppInviteSpent(appInviteUuid) {
const signUpChallengeCompleted = await SignUpChallengeCompleted.findOne({ const signUpChallengeCompleted = await SignUpChallengeCompleted.findOne({
where: { where: {
app_invite_uuid: appInviteUuid app_invite_uuid: appInviteUuid,
} },
}) });
if (signUpChallengeCompleted) { if (signUpChallengeCompleted) {
return true; return true;
@ -37,82 +41,71 @@ async function createAppInvite(inviterPubKey) {
return await AppInviteCreated.create({ return await AppInviteCreated.create({
uuid: uuid.v7(), uuid: uuid.v7(),
inviter_pub_key: inviterPubKey, inviter_pub_key: inviterPubKey,
created_at: new Date().toISOString() created_at: new Date().toISOString(),
} });
);
} }
async function createSignUpChallenge(appInviteUuid) { async function createSignUpChallenge(appInviteUuid) {
if (!(await appInviteExists(appInviteUuid))) { if (!(await appInviteExists(appInviteUuid))) {
throw new errors.NotFoundError("Invite doesn't exist.") throw new errors.NotFoundError("Invite doesn't exist.");
} }
if (await isAppInviteSpent(appInviteUuid)) { if (await isAppInviteSpent(appInviteUuid)) {
throw new errors.AlreadyUsedError("Invite has already been used.") throw new errors.AlreadyUsedError('Invite has already been used.');
} }
const nostrChallenge = await nostrService.createNostrChallenge() const nostrChallenge = await nostrService.createNostrChallenge();
return await SignUpChallengeCreated.create({ return await SignUpChallengeCreated.create({
'uuid': uuid.v7(), uuid: uuid.v7(),
nostr_challenge_uuid: nostrChallenge.uuid, nostr_challenge_uuid: nostrChallenge.uuid,
app_invite_uuid: appInviteUuid, app_invite_uuid: appInviteUuid,
created_at: new Date().toISOString() created_at: new Date().toISOString(),
} });
)
} }
async function verifySignUpChallenge(signedEvent) { async function verifySignUpChallenge(signedEvent) {
const challengeTag = signedEvent.tags.find((tag) => tag[0] === 'challenge');
const challengeTag = signedEvent.tags.find(tag => tag[0] === "challenge");
const challenge = challengeTag[1]; const challenge = challengeTag[1];
const nostrChallenge = await nostrService.getNostrChallenge( const nostrChallenge = await nostrService.getNostrChallenge(null, challenge);
null, challenge
);
const signUpChallenge = await SignUpChallengeCreated.findOne({ const signUpChallenge = await SignUpChallengeCreated.findOne({
where: { where: {
nostr_challenge_uuid: nostrChallenge.uuid nostr_challenge_uuid: nostrChallenge.uuid,
} },
}) });
if (await nostrService.hasNostrChallengeBeenCompleted(challenge)) { if (await nostrService.hasNostrChallengeBeenCompleted(challenge)) {
throw new errors.AlreadyUsedError("This challenge has already been used."); throw new errors.AlreadyUsedError('This challenge has already been used.');
} }
const completedNostrChallenge = await nostrService.verifyNostrChallenge(signedEvent); const completedNostrChallenge =
await nostrService.verifyNostrChallenge(signedEvent);
const completedSignUpChallenge = await SignUpChallengeCompleted.create( const completedSignUpChallenge = await SignUpChallengeCompleted.create({
{ uuid: uuid.v7(),
'uuid': uuid.v7(),
nostr_challenge_completed_uuid: completedNostrChallenge.uuid, nostr_challenge_completed_uuid: completedNostrChallenge.uuid,
app_invite_uuid: signUpChallenge.app_invite_uuid, app_invite_uuid: signUpChallenge.app_invite_uuid,
public_key: completedNostrChallenge.public_key, public_key: completedNostrChallenge.public_key,
created_at: new Date().toISOString() created_at: new Date().toISOString(),
} });
);
return completedSignUpChallenge; return completedSignUpChallenge;
} }
async function isPublicKeySignedUp(publicKey) { async function isPublicKeySignedUp(publicKey) {
const signUpChallengeCompleted = await SignUpChallengeCompleted.findOne( const signUpChallengeCompleted = await SignUpChallengeCompleted.findOne({
{
where: { where: {
public_key: publicKey public_key: publicKey,
} },
} });
)
if (signUpChallengeCompleted) { if (signUpChallengeCompleted) {
return true; return true;
} }
return false; return false;
} }
module.exports = { module.exports = {
@ -122,5 +115,5 @@ module.exports = {
createAppInvite, createAppInvite,
createSignUpChallenge, createSignUpChallenge,
verifySignUpChallenge, verifySignUpChallenge,
isPublicKeySignedUp isPublicKeySignedUp,
}; };

View file

@ -1,77 +1,78 @@
const uuid = require("uuid"); const uuid = require('uuid');
const crypto = require("crypto"); const crypto = require('crypto');
const { Op, TimeoutError } = require('sequelize'); const { Op, TimeoutError } = require('sequelize');
const { verifyEvent } = require("nostr-tools"); const { verifyEvent } = require('nostr-tools');
const NostrChallengeCreated = require('../models/NostrChallengeCreated'); const NostrChallengeCreated = require('../models/NostrChallengeCreated');
const NostrChallengeCompleted = require("../models/NostrChallengeCompleted"); const NostrChallengeCompleted = require('../models/NostrChallengeCompleted');
const constants = require('../constants'); const constants = require('../constants');
const errors = require('../errors'); const errors = require('../errors');
async function createNostrChallenge() { async function createNostrChallenge() {
const currentTimestamp = new Date(); const currentTimestamp = new Date();
const expiryTimestamp = new Date(currentTimestamp.getTime()); const expiryTimestamp = new Date(currentTimestamp.getTime());
expiryTimestamp.setSeconds(expiryTimestamp.getSeconds() + constants.DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS); expiryTimestamp.setSeconds(
expiryTimestamp.getSeconds() +
constants.DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS
);
const nostrChallenge = await NostrChallengeCreated.create({ const nostrChallenge = await NostrChallengeCreated.create({
'uuid': uuid.v7(), uuid: uuid.v7(),
challenge: crypto.randomBytes(32).toString("hex"), challenge: crypto.randomBytes(32).toString('hex'),
expires_at: expiryTimestamp.toISOString(), expires_at: expiryTimestamp.toISOString(),
created_at: currentTimestamp.toISOString() created_at: currentTimestamp.toISOString(),
}); });
return nostrChallenge; return nostrChallenge;
} }
async function getNostrChallenge(nostrChallengeUuid = null, challenge = null) { async function getNostrChallenge(nostrChallengeUuid = null, challenge = null) {
if (nostrChallengeUuid) { if (nostrChallengeUuid) {
return await NostrChallengeCreated.findOne({ return await NostrChallengeCreated.findOne({
where: { where: {
'uuid': nostrChallengeUuid uuid: nostrChallengeUuid,
} },
}) });
} }
if (challenge) { if (challenge) {
return await NostrChallengeCreated.findOne({ return await NostrChallengeCreated.findOne({
where: { where: {
challenge challenge,
} },
}) });
} }
throw Error('You need to pass a uuid or a challenge.') throw Error('You need to pass a uuid or a challenge.');
} }
async function verifyNostrChallenge(signedEvent) { async function verifyNostrChallenge(signedEvent) {
const challengeTag = signedEvent.tags.find(tag => tag[0] === "challenge"); const challengeTag = signedEvent.tags.find((tag) => tag[0] === 'challenge');
const challenge = challengeTag[1]; const challenge = challengeTag[1];
if (!(await isNostrChallengeFresh(challenge))) { if (!(await isNostrChallengeFresh(challenge))) {
throw TimeoutError("Challenge expired, request new one."); throw TimeoutError('Challenge expired, request new one.');
} }
if (await hasNostrChallengeBeenCompleted(challenge)) { if (await hasNostrChallengeBeenCompleted(challenge)) {
throw new errors.AlreadyUsedError("Challenge already used, request new one."); throw new errors.AlreadyUsedError(
'Challenge already used, request new one.'
);
} }
const isSignatureValid = verifyEvent(signedEvent); const isSignatureValid = verifyEvent(signedEvent);
if (!isSignatureValid) { if (!isSignatureValid) {
throw new errors.InvalidSignatureError("Signature is not valid."); throw new errors.InvalidSignatureError('Signature is not valid.');
} }
return await NostrChallengeCompleted.create({ return await NostrChallengeCompleted.create({
'uuid': uuid.v7(), uuid: uuid.v7(),
challenge: challenge, challenge: challenge,
signed_event: signedEvent, signed_event: signedEvent,
public_key: signedEvent.pubkey, public_key: signedEvent.pubkey,
created_at: new Date().toISOString() created_at: new Date().toISOString(),
} });
);
} }
async function isNostrChallengeFresh(challengeString) { async function isNostrChallengeFresh(challengeString) {
@ -79,9 +80,9 @@ async function isNostrChallengeFresh(challengeString) {
where: { where: {
challenge: challengeString, challenge: challengeString,
expires_at: { expires_at: {
[Op.gt]: new Date() [Op.gt]: new Date(),
} },
} },
}); });
if (nostrChallenge) { if (nostrChallenge) {
@ -91,13 +92,11 @@ async function isNostrChallengeFresh(challengeString) {
} }
async function hasNostrChallengeBeenCompleted(challengeString) { async function hasNostrChallengeBeenCompleted(challengeString) {
const completedNostrChallenge = await NostrChallengeCompleted.findOne( const completedNostrChallenge = await NostrChallengeCompleted.findOne({
{
where: { where: {
challenge: challengeString challenge: challengeString,
} },
} });
);
if (completedNostrChallenge) { if (completedNostrChallenge) {
return true; return true;
@ -105,11 +104,10 @@ async function hasNostrChallengeBeenCompleted(challengeString) {
return false; return false;
} }
module.exports = { module.exports = {
createNostrChallenge, createNostrChallenge,
getNostrChallenge, getNostrChallenge,
verifyNostrChallenge, verifyNostrChallenge,
isNostrChallengeFresh, isNostrChallengeFresh,
hasNostrChallengeBeenCompleted hasNostrChallengeBeenCompleted,
}; };

View file

@ -3,28 +3,24 @@ const ContactDetailsSet = require('../models/ContactDetailsSet');
const NymSet = require('../models/NymSet'); const NymSet = require('../models/NymSet');
async function setContactDetails(publicKey, encryptedContactDetails) { async function setContactDetails(publicKey, encryptedContactDetails) {
return await ContactDetailsSet.create( return await ContactDetailsSet.create({
{ uuid: uuid.v7(),
'uuid': uuid.v7(),
public_key: publicKey, public_key: publicKey,
encrypted_contact_details: encryptedContactDetails, encrypted_contact_details: encryptedContactDetails,
created_at: new Date().toISOString() created_at: new Date().toISOString(),
} });
)
} }
async function setNym(publicKey, nym) { async function setNym(publicKey, nym) {
return await NymSet.create( return await NymSet.create({
{ uuid: uuid.v7(),
'uuid': uuid.v7(),
public_key: publicKey, public_key: publicKey,
nym: nym, nym: nym,
created_at: new Date().toISOString() created_at: new Date().toISOString(),
} });
)
} }
module.exports = { module.exports = {
setContactDetails, setContactDetails,
setNym setNym,
}; };

View file

@ -9,20 +9,22 @@ const constants = require('../constants');
async function createSession(sessionUuid) { async function createSession(sessionUuid) {
const currentTimestamp = new Date(); const currentTimestamp = new Date();
const expiryTimestamp = new Date(currentTimestamp.getTime()); const expiryTimestamp = new Date(currentTimestamp.getTime());
expiryTimestamp.setSeconds(expiryTimestamp.getSeconds() + constants.DEFAULT_SESSION_DURATION_SECONDS); expiryTimestamp.setSeconds(
expiryTimestamp.getSeconds() + constants.DEFAULT_SESSION_DURATION_SECONDS
);
return await SessionCreated.create({ return await SessionCreated.create({
uuid: sessionUuid, uuid: sessionUuid,
created_at: currentTimestamp.toISOString(), created_at: currentTimestamp.toISOString(),
expires_at: expiryTimestamp.toISOString() expires_at: expiryTimestamp.toISOString(),
}); });
} }
async function isSessionValid(sessionUuid) { async function isSessionValid(sessionUuid) {
const currentSession = await SessionCreated.findOne({ const currentSession = await SessionCreated.findOne({
where: { where: {
'uuid': sessionUuid uuid: sessionUuid,
} },
}); });
if (!currentSession) { if (!currentSession) {
@ -38,30 +40,27 @@ async function isSessionValid(sessionUuid) {
async function relateSessionToPublicKey(sessionUuid, publicKey) { async function relateSessionToPublicKey(sessionUuid, publicKey) {
if (!(await isSessionValid(sessionUuid))) { if (!(await isSessionValid(sessionUuid))) {
throw Error("Session is not valid anymore."); throw Error('Session is not valid anymore.');
} }
if (!(await invitesService.isPublicKeySignedUp(publicKey))) { if (!(await invitesService.isPublicKeySignedUp(publicKey))) {
throw Error("Public key is not signed up."); throw Error('Public key is not signed up.');
} }
return SessionRelatedToPublickey.create({ return SessionRelatedToPublickey.create({
'uuid': uuid.v7(), uuid: uuid.v7(),
session_uuid: sessionUuid, session_uuid: sessionUuid,
public_key: publicKey, public_key: publicKey,
created_at: new Date().toISOString() created_at: new Date().toISOString(),
}); });
} }
async function isSessionAuthorized(sessionUuid) { async function isSessionAuthorized(sessionUuid) {
const isSessionRelatedToPublicKey = await SessionRelatedToPublickey.findOne( const isSessionRelatedToPublicKey = await SessionRelatedToPublickey.findOne({
{
where: { where: {
session_uuid: sessionUuid session_uuid: sessionUuid,
} },
} });
);
if (isSessionRelatedToPublicKey) { if (isSessionRelatedToPublicKey) {
return true; return true;
@ -71,16 +70,13 @@ async function isSessionAuthorized(sessionUuid) {
} }
async function getPublicKeyRelatedToSession(sessionUuid) { async function getPublicKeyRelatedToSession(sessionUuid) {
const sessionRelatedToPublickey = await SessionRelatedToPublickey.findOne( const sessionRelatedToPublickey = await SessionRelatedToPublickey.findOne({
{
where: { where: {
session_uuid: sessionUuid session_uuid: sessionUuid,
} },
} });
);
return sessionRelatedToPublickey.public_key; return sessionRelatedToPublickey.public_key;
} }
module.exports = { module.exports = {
@ -88,5 +84,5 @@ module.exports = {
isSessionValid, isSessionValid,
relateSessionToPublicKey, relateSessionToPublicKey,
isSessionAuthorized, isSessionAuthorized,
getPublicKeyRelatedToSession getPublicKeyRelatedToSession,
} };

View file

@ -1,53 +1,77 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<head>
<title>Crear perfil</title> <title>Crear perfil</title>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/css/seca.css"> <link rel="stylesheet" href="/css/seca.css" />
<script src="/javascript/createProfile.js"></script> <script src="/javascript/createProfile.js"></script>
</head> </head>
<body> <body>
<h1>Crea tu perfil</h1> <h1>Crea tu perfil</h1>
<p>Tu clave de Nostr ya es parte de la seca.</p> <p>Tu clave de Nostr ya es parte de la seca.</p>
<p>Añade detalles a tu perfil para poder empezar a comerciar.</p> <p>Añade detalles a tu perfil para poder empezar a comerciar.</p>
</p> <hr />
<hr>
<form onsubmit="return false"> <form onsubmit="return false">
<label>Pseudónimo (Nym):<input type="text" name="nym" id="nym-input"></label> <label
>Pseudónimo (Nym):<input type="text" name="nym" id="nym-input"
/></label>
<div id="contacts"> <div id="contacts">
<p>Añade métodos de contacto para poder hablar con otros miembros.</p> <p>Añade métodos de contacto para poder hablar con otros miembros.</p>
<div class="badges"> <div class="badges">
<div class="badge" data-type="whatsapp">📱 Teléfono <input><button <div class="badge" data-type="whatsapp">
class="contact-detail-add-button">Añadir</button></div> 📱 Teléfono <input /><button class="contact-detail-add-button">
<div class="badge" data-type="whatsapp">📱 WhatsApp <input><button Añadir
class="contact-detail-add-button">Añadir</button></div> </button>
<div class="badge" data-type="telegram">📩 Telegram <input><button </div>
class="contact-detail-add-button"></button>>Añadir</button></div> <div class="badge" data-type="whatsapp">
<div class="badge" data-type="email">📧 Email <input><button 📱 WhatsApp <input /><button class="contact-detail-add-button">
class="contact-detail-add-button">Añadir</button></div> Añadir
<div class="badge" data-type="nostr">📧 Nostr <input><button </button>
class="contact-detail-add-button">Añadir</button></div> </div>
<div class="badge" data-type="signal">📧 Signal <input><button <div class="badge" data-type="telegram">
class="contact-detail-add-button">Añadir</button></div> 📩 Telegram <input /><button class="contact-detail-add-button">
<div class="badge" data-type="matrix">📧 Matrix <input><button Añadir
class="contact-detail-add-button">Añadir</button></div> </button>
<div class="badge" data-type="xmpp">📧 XMPP <input><button </div>
class="contact-detail-add-button">Añadir</button></div> <div class="badge" data-type="email">
<div class="badge" data-type="simplex">📧 Simplex <input><button 📧 Email <input /><button class="contact-detail-add-button">
class="contact-detail-add-button">Añadir</button></div> Añadir
</button>
</div>
<div class="badge" data-type="nostr">
📧 Nostr <input /><button class="contact-detail-add-button">
Añadir
</button>
</div>
<div class="badge" data-type="signal">
📧 Signal <input /><button class="contact-detail-add-button">
Añadir
</button>
</div>
<div class="badge" data-type="matrix">
📧 Matrix <input /><button class="contact-detail-add-button">
Añadir
</button>
</div>
<div class="badge" data-type="xmpp">
📧 XMPP <input /><button class="contact-detail-add-button">
Añadir
</button>
</div>
<div class="badge" data-type="simplex">
📧 Simplex <input /><button class="contact-detail-add-button">
Añadir
</button>
</div>
</div> </div>
</div> </div>
<div id="created-contact-details-section"> <div id="created-contact-details-section">
<p>Contactos añadidos</p> <p>Contactos añadidos</p>
<div id="created-contact-details-list"> <div id="created-contact-details-list"></div>
</div>
</div> </div>
<button id="submit-details-button" type="submit">Crear tu perfil</button> <button id="submit-details-button" type="submit">Crear tu perfil</button>
</form> </form>
</body> </body>
</html> </html>

View file

@ -1,17 +1,13 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<head> <meta charset="UTF-8" />
<meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Error</title> <title>Error</title>
</head> </head>
<body> <body>
<h1>Error</h1> <h1>Error</h1>
<p> <p><%= message %></p>
<%= message %> </body>
</p>
</body>
</html> </html>

View file

@ -1,23 +1,26 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<head>
<title>Hello World</title> <title>Hello World</title>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="/javascript/index.js"></script> <script src="/javascript/index.js"></script>
</head> </head>
<body> <body>
<h1>Bienvenido a la seca</h1> <h1>Bienvenido a la seca</h1>
<p>Usa Nostr para logearte</p> <p>Usa Nostr para logearte</p>
<form onsubmit="login();return false"> <form onsubmit="login();return false">
<button type="submit">Login con extensión de Nostr</button> <button type="submit">Login con extensión de Nostr</button>
</form> </form>
<p> <p>
¿No tienes cuenta de Nostr? <a href="https://start.njump.me/" target="_blank" rel="noopener noreferrer">Crea una ¿No tienes cuenta de Nostr?
gratis</a>. <a
href="https://start.njump.me/"
target="_blank"
rel="noopener noreferrer"
>Crea una gratis</a
>.
</p> </p>
</body> </body>
</html> </html>

View file

@ -1,53 +1,88 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<head>
<title>Invite Details</title> <title>Invite Details</title>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="/javascript/invite.js"></script> <script src="/javascript/invite.js"></script>
</head> </head>
<body> <body>
<h1>¡Has sido invitado!</h1> <h1>¡Has sido invitado!</h1>
<p>Has sido invitado a la seca.</p> <p>Has sido invitado a la seca.</p>
<p>Invite UUID: <%= invite.uuid %> <p>Invite UUID: <%= invite.uuid %></p>
</p>
<p>Usa tu extensión de Nostr para darte de alta:</p> <p>Usa tu extensión de Nostr para darte de alta:</p>
<form onsubmit="acceptInvite();return false"> <form onsubmit="acceptInvite();return false">
<button id="nostr-signup" type="submit">Alta con Nostr</button> <button id="nostr-signup" type="submit">Alta con Nostr</button>
</form> </form>
<div id="rejected-nostr-nudges" style="display:none"> <div id="rejected-nostr-nudges" style="display: none">
<p>Ups, parece que no has aceptado que usemos tus claves. Si te has equivocado, puedes intentarlo de nuevo.</p> <p>
Ups, parece que no has aceptado que usemos tus claves. Si te has
equivocado, puedes intentarlo de nuevo.
</p>
</div> </div>
<div id="no-extension-nudges" style="display:none"> <div id="no-extension-nudges" style="display: none">
<p>¡Atención! No se ha encontrado una extensión de Nostr en tu navegador. Puedes usar: </p> <p>
¡Atención! No se ha encontrado una extensión de Nostr en tu navegador.
Puedes usar:
</p>
<ul> <ul>
<li>Firefox <li>
Firefox
<ul> <ul>
<li><a href="https://addons.mozilla.org/en-US/firefox/addon/alby/" target="_blank" <li>
rel="noopener noreferrer">Alby</a></li> <a
<li><a href="https://addons.mozilla.org/en-US/firefox/addon/nos2x-fox/" target="_blank" href="https://addons.mozilla.org/en-US/firefox/addon/alby/"
rel="noopener noreferrer">nos2x-fox</a></li> target="_blank"
rel="noopener noreferrer"
>Alby</a
>
</li>
<li>
<a
href="https://addons.mozilla.org/en-US/firefox/addon/nos2x-fox/"
target="_blank"
rel="noopener noreferrer"
>nos2x-fox</a
>
</li>
</ul> </ul>
</li> </li>
<li> <li>
Chrome Chrome
<ul> <ul>
<li><a href="https://chromewebstore.google.com/detail/alby-bitcoin-wallet-for-l/iokeahhehimjnekafflcihljlcjccdbe?pli=1" <li>
target="_blank" rel="noopener noreferrer">Alby</a></li> <a
<li><a href="https://chromewebstore.google.com/detail/nos2x/kpgefcfmnafjgpblomihpgmejjdanjjp" href="https://chromewebstore.google.com/detail/alby-bitcoin-wallet-for-l/iokeahhehimjnekafflcihljlcjccdbe?pli=1"
target="_blank" rel="noopener noreferrer">nos2x</a></li> target="_blank"
rel="noopener noreferrer"
>Alby</a
>
</li>
<li>
<a
href="https://chromewebstore.google.com/detail/nos2x/kpgefcfmnafjgpblomihpgmejjdanjjp"
target="_blank"
rel="noopener noreferrer"
>nos2x</a
>
</li>
</ul> </ul>
</li> </li>
</ul> </ul>
</div> </div>
<div id="sign-up-success" style="display:none"> <div id="sign-up-success" style="display: none">
<p>¡Bien! Hemos dado de alta tu clave de Nostr.</p> <p>¡Bien! Hemos dado de alta tu clave de Nostr.</p>
<p>Te vamos a redirigir a la seca, espera un momento.</p> <p>Te vamos a redirigir a la seca, espera un momento.</p>
</div> </div>
<p>¿No tienes cuenta de Nostr? <a href="https://start.njump.me/" target="_blank" rel="noopener noreferrer">Crea <p>
una aquí.</a< /p> ¿No tienes cuenta de Nostr?
</body> <a
href="https://start.njump.me/"
target="_blank"
rel="noopener noreferrer"
>Crea una aquí.</a
>
</p>
</body>
</html> </html>

View file

@ -1,20 +1,17 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<head>
<title>Invite Details</title> <title>Invite Details</title>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head> </head>
<body> <body>
<h1>Invite Details</h1> <h1>Invite Details</h1>
<p>Invite UUID: <%= invite.uuid %> <p>Invite UUID: <%= invite.uuid %></p>
</p>
<h2>Wait right there buddy... that invite has been spent already!</h2> <h2>Wait right there buddy... that invite has been spent already!</h2>
<form> <form>
<button onclick="" disabled>Click to accept invite with Nostr</button> <button onclick="" disabled>Click to accept invite with Nostr</button>
</form> </form>
</body> </body>
</html> </html>

View file

@ -1,15 +1,16 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<head>
<title>Private page</title> <title>Private page</title>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head> </head>
<body> <body>
<h1>Private page</h1> <h1>Private page</h1>
<p>If you are here, it's because your npub is white listed, and your session cookie is related to it.</p> <p>
</body> If you are here, it's because your npub is white listed, and your session
cookie is related to it.
</p>
</body>
</html> </html>