Skip to content

Commit 8ff20f0

Browse files
committed
Separate free and premium notedeck install instructions
Closes: damus-io/notedeck#829 Signed-off-by: Daniel D’Aquino <[email protected]>
1 parent abcdd1f commit 8ff20f0

11 files changed

+236
-29
lines changed

.type_check.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ src/web_auth.js(7,35): error TS2307: Cannot find module 'nostr-tools/pure' or it
2525
src/web_auth.js(9,48): error TS2307: Cannot find module 'nostr-tools/pool' or its corresponding type declarations.
2626
src/web_auth.js(11,32): error TS2307: Cannot find module 'nostr-tools/pool' or its corresponding type declarations.
2727
src/web_auth.js(43,25): error TS2365: Operator '+' cannot be applied to types 'number' and 'string | number'.
28-
src/web_auth.js(161,9): error TS2365: Operator '>' cannot be applied to types 'number' and 'string | number'.
2928
test/controllers/mock_iap_controller.js(85,60): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
3029
test/controllers/purple_test_controller.js(49,5): error TS2322: Type 'boolean' is not assignable to type 'string'.
3130
test/controllers/purple_test_controller.js(55,5): error TS2322: Type 'number' is not assignable to type 'string'.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ The Damus API backend for Damus Purple and other functionality.
1212

1313
- `DB_PATH`: Path to the folder where to save mdb files.
1414
- `TESTFLIGHT_URL`: URL for the TestFlight app (optional)
15-
- `NOTEDECK_INSTALL_MD`: URL for the notedeck installation instructions markdown
16-
- `NO_AUTH_WALL_NOTEDECK_INSTALL`: Disables the authentication wall for the notedeck installation instructions when set to `"true"`.
15+
- `NOTEDECK_INSTALL_MD`: Path to the freely available notedeck installation instructions (markdown file)
16+
- `NOTEDECK_INSTALL_PREMIUM_MD`: Path to the premium notedeck installation instructions (markdown file)
1717

1818
#### Translations
1919

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Installing Notedeck
2+
3+
Thank you for your interest in the Notedeck Beta release! We are excited to have you try out our software. Below are the instructions for installing Notedeck on your system.
4+
5+
If you encounter any issues, please [contact us]([email protected]).
6+
7+
## Binaries
8+
9+
Please find the download link for your operating system below.
10+
11+
- macOS (Apple Silicon): [notedeck_macos_silicon.dmg](#)
12+
- macOS (Intel): [notedeck_macos_intel.dmg](#)
13+
- Linux Debian package: [notedeck_linux_debian.deb](#)
14+
- Linux x86 generic: [notedeck_linux_generic.zip](#)
15+
- Linux ARM generic: [notedeck_linux_generic.zip](#)

notedeck-install-instructions.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
Thank you for your interest in the Notedeck Alpha release! We are excited to have you try out our software. Below are the instructions for installing Notedeck on your system.
44

5+
If you are a Purple user, you can now access our beta release! To access the beta install instructions, click [here](/purple/login).
6+
57
If you encounter any issues, please [contact us]([email protected]).
68

79
## Binaries

src/router_config.js

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -445,26 +445,28 @@ function config_router(app) {
445445
return
446446
});
447447

448-
if(process.env.NO_AUTH_WALL_NOTEDECK_INSTALL == "true") {
449-
router.get('/notedeck-install-instructions', async (req, res) => {
450-
provide_notedeck_instructions(req, res)
451-
});
452-
}
453-
else {
454-
router.get('/notedeck-install-instructions', app.web_auth_manager.require_web_auth.bind(app.web_auth_manager), async (req, res) => {
455-
const pubkey = req.authorized_pubkey
456-
const { account, user_id } = get_account_and_user_id(app, pubkey)
457-
if (!account) {
458-
simple_response(res, 401)
459-
return
460-
}
461-
const account_info = get_account_info_payload(user_id, account, true)
462-
if(account_info.active == true) {
463-
provide_notedeck_instructions(req, res)
464-
}
448+
router.get('/notedeck-install-instructions', app.web_auth_manager.use_web_auth.bind(app.web_auth_manager), async (req, res) => {
449+
if(!req.authorized_pubkey) {
450+
provide_notedeck_instructions(req, res, false) // Provides free download instructions
465451
return
466-
});
467-
}
452+
}
453+
454+
const { account, user_id } = get_account_and_user_id(app, req.authorized_pubkey)
455+
if (!account) {
456+
simple_response(res, 401)
457+
return
458+
}
459+
460+
const account_info = get_account_info_payload(user_id, account, true)
461+
if (account_info.active == true) {
462+
provide_notedeck_instructions(req, res, true) // Provide premium download instructions
463+
return
464+
}
465+
else {
466+
provide_notedeck_instructions(req, res, false) // Provide free download instructions
467+
return
468+
}
469+
});
468470

469471
// MARK: Admin routes
470472

@@ -681,8 +683,8 @@ function get_allowed_cors_origins() {
681683
}
682684
}
683685

684-
async function provide_notedeck_instructions(req, res) {
685-
const installInstructionsPath = path.resolve(process.env.NOTEDECK_INSTALL_MD);
686+
async function provide_notedeck_instructions(req, res, user_authenticated) {
687+
const installInstructionsPath = user_authenticated == true ? path.resolve(process.env.NOTEDECK_INSTALL_PREMIUM_MD) : path.resolve(process.env.NOTEDECK_INSTALL_MD);
686688
try {
687689
const installInstructions = fs.readFileSync(installInstructionsPath, { encoding: 'utf8' });
688690
json_response(res, { value: installInstructions });

src/web_auth.js

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class WebAuthManager {
2626
this.dbs = dbs
2727
this.pool = pool
2828
this.otp_max_tries = process.env.OTP_MAX_TRIES || DEFAULT_OTP_MAX_TRIES
29-
this.session_expiry = process.env.SESSION_EXPIRY || DEFAULT_SESSION_EXPIRY
29+
this.session_expiry = parseInt(process.env.SESSION_EXPIRY) || DEFAULT_SESSION_EXPIRY
3030
this.otp_expiry = process.env.OTP_EXPIRY || DEFAULT_OTP_EXPIRY
3131
}
3232

@@ -128,7 +128,7 @@ class WebAuthManager {
128128
// MARK: Middleware
129129

130130
/**
131-
* Middleware to check if a user is authenticated
131+
* Middleware to check and require a user to be authenticated
132132
* @param {object} req - The Express request object
133133
* @param {object} res - The Express response object
134134
* @param {function} next - The next middleware function
@@ -167,6 +167,49 @@ class WebAuthManager {
167167
req.authorized_pubkey = session_data.pubkey;
168168
next();
169169
}
170+
171+
/**
172+
* Middleware to check if a user is authenticated, but does not require authentication
173+
* @param {object} req - The Express request object
174+
* @param {object} res - The Express response object
175+
* @param {function} next - The next middleware function
176+
*/
177+
async use_web_auth(req, res, next) {
178+
const auth_header = req.header('Authorization');
179+
180+
if (!auth_header) {
181+
next();
182+
return;
183+
}
184+
185+
const [auth_type, token] = auth_header.split(' ');
186+
if (auth_type !== 'Bearer') {
187+
next();
188+
return;
189+
}
190+
191+
if (!token) {
192+
next();
193+
return;
194+
}
195+
196+
const session_data = await this.dbs.sessions.get(token);
197+
if (!session_data) {
198+
next();
199+
return;
200+
}
201+
202+
// Check if the session has expired
203+
if (current_time() - session_data.created_at > this.session_expiry) {
204+
next();
205+
return;
206+
}
207+
208+
// User authenticated!
209+
210+
req.authorized_pubkey = session_data.pubkey;
211+
next();
212+
}
170213
}
171214

172215

test/controllers/purple_test_client.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,19 @@ class PurpleTestClient {
3838
return await this.get('/accounts/' + this.public_key, options)
3939
}
4040

41+
/**
42+
* Gets the notedeck install instructions
43+
*
44+
* @param {PurpleTestClientRequestOptions} options - The request options
45+
* @returns {Promise<Object>} The response
46+
*/
47+
async get_notedeck_install_instructions(options = {}) {
48+
return await this.get('/notedeck-install-instructions', options)
49+
}
50+
4151
/**
4252
* Gets the product template options.
43-
*
53+
*
4454
* @param {PurpleTestClientRequestOptions} options - The request options
4555
* @returns {Promise<Object>} The product information
4656
*/

test/controllers/purple_test_controller.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ class PurpleTestController {
5757
process.env.OTP_EXPIRY = 60*5
5858
process.env.TESTFLIGHT_URL = "https://testflight.apple.com/join/abc123"
5959
process.env.NOTEDECK_INSTALL_MD = "./notedeck-install-instructions.md"
60+
process.env.NOTEDECK_INSTALL_PREMIUM_MD = "./notedeck-install-instructions-premium.md"
61+
this.env = process.env
6062
}
6163

6264
setup_stubs() {
@@ -193,8 +195,23 @@ class PurpleTestController {
193195
const check_invoice_status_response = await this.clients[pubkey].check_invoice(verify_checkout_response.body.id);
194196
this.t.same(check_invoice_status_response.status, 200)
195197
}
196-
197-
198+
199+
async login(pubkey) {
200+
// Let's request an OTP
201+
const test_otp = "432931";
202+
this.web_auth_controller.set_next_random_otp(test_otp);
203+
const otp_response = await this.clients[pubkey].request_otp();
204+
this.t.same(otp_response.statusCode, 200);
205+
this.t.same(otp_response.body, { success: true });
206+
207+
// Login with the correct OTP
208+
const login_response = await this.clients[pubkey].verify_otp(test_otp);
209+
this.t.same(login_response.statusCode, 200);
210+
this.t.same(login_response.body.valid, true);
211+
this.t.ok(login_response.body.session_token);
212+
return login_response.body.session_token
213+
}
214+
198215
// MARK: - Account UUID control
199216

200217
/**
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"use strict";
2+
// @ts-check
3+
4+
const test = require('tap').test;
5+
const { PurpleTestController } = require('./controllers/purple_test_controller.js');
6+
const { PURPLE_ONE_MONTH } = require('../src/invoicing.js');
7+
const { v4: uuidv4 } = require('uuid');
8+
const path = require('path');
9+
const fs = require('fs');
10+
11+
const PREMIUM_INSTRUCTIONS = 'premium'
12+
const FREE_INSTRUCTIONS = 'free'
13+
14+
function get_notedeck_instructions(purple_test_controller, instruction_type) {
15+
const installInstructionsPath = instruction_type == PREMIUM_INSTRUCTIONS ? path.resolve(purple_test_controller.env.NOTEDECK_INSTALL_PREMIUM_MD) : path.resolve(purple_test_controller.env.NOTEDECK_INSTALL_MD);
16+
return fs.readFileSync(installInstructionsPath, { encoding: 'utf8' });
17+
}
18+
19+
test('Notedeck free install instructions flow', async (t) => {
20+
// Initialize the PurpleTestController
21+
const purple_api_controller = await PurpleTestController.new(t);
22+
23+
// Instantiate a new client
24+
const user_pubkey_1 = purple_api_controller.new_client();
25+
26+
const notedeck_install_response = await purple_api_controller.clients[user_pubkey_1].get_notedeck_install_instructions()
27+
t.same(notedeck_install_response.statusCode, 200);
28+
t.same(notedeck_install_response.body.value, get_notedeck_instructions(purple_api_controller, FREE_INSTRUCTIONS));
29+
30+
t.end();
31+
});
32+
33+
test('Notedeck premium install instructions flow', async (t) => {
34+
// Initialize the PurpleTestController
35+
const purple_api_controller = await PurpleTestController.new(t);
36+
37+
// Instantiate a new client
38+
const user_pubkey_1 = purple_api_controller.new_client();
39+
40+
// Let's get them an account
41+
await purple_api_controller.ln_flow_buy_subscription(user_pubkey_1, PURPLE_ONE_MONTH);
42+
43+
// Get the account info
44+
const response = await purple_api_controller.clients[user_pubkey_1].get_account();
45+
t.same(response.statusCode, 200);
46+
47+
// Let's login to get a session token
48+
// (We could probably get it from the ln subscription flow part, but we are not testing that here, so it's fine)
49+
let session_token = await purple_api_controller.login(user_pubkey_1);
50+
51+
const notedeck_install_response = await purple_api_controller.clients[user_pubkey_1].get_notedeck_install_instructions({ session_token: session_token })
52+
t.same(notedeck_install_response.statusCode, 200);
53+
t.same(notedeck_install_response.body.value, get_notedeck_instructions(purple_api_controller, PREMIUM_INSTRUCTIONS));
54+
55+
t.end();
56+
});
57+
58+
test('Notedeck unauthorized premium install instructions flow', async (t) => {
59+
// Initialize the PurpleTestController
60+
const purple_api_controller = await PurpleTestController.new(t);
61+
62+
// Instantiate a new client but don't get an account
63+
const user_pubkey_1 = purple_api_controller.new_client();
64+
65+
// Get the account info
66+
const response = await purple_api_controller.clients[user_pubkey_1].get_account();
67+
t.same(response.statusCode, 404);
68+
69+
const notedeck_install_response = await purple_api_controller.clients[user_pubkey_1].get_notedeck_install_instructions({ session_token: "fakesessiontoken" })
70+
t.same(notedeck_install_response.statusCode, 200);
71+
t.same(notedeck_install_response.body.value, get_notedeck_instructions(purple_api_controller, FREE_INSTRUCTIONS));
72+
73+
t.end();
74+
});
75+
76+
77+
test('Notedeck expired account install instructions flow', async (t) => {
78+
// Initialize the PurpleTestController
79+
const purple_api_controller = await PurpleTestController.new(t);
80+
purple_api_controller.set_current_time(1706659200) // 2024-01-31 00:00:00 UTC
81+
82+
// Instantiate a new client
83+
const user_pubkey_1 = purple_api_controller.new_client();
84+
85+
const initial_account_info_response = await purple_api_controller.clients[user_pubkey_1].get_account();
86+
t.same(initial_account_info_response.statusCode, 404);
87+
88+
// Buy a one month subscription
89+
await purple_api_controller.ln_flow_buy_subscription(user_pubkey_1, PURPLE_ONE_MONTH);
90+
91+
// Check expiry
92+
const account_info_response_1 = await purple_api_controller.clients[user_pubkey_1].get_account();
93+
t.same(account_info_response_1.statusCode, 200);
94+
t.same(account_info_response_1.body.expiry, purple_api_controller.current_time() + 30 * 24 * 60 * 60);
95+
t.same(account_info_response_1.body.active, true);
96+
97+
// Move time forward by 35 days, and make sure the account is not active anymore
98+
purple_api_controller.set_current_time(purple_api_controller.current_time() + 35 * 24 * 60 * 60);
99+
const account_info_response_2 = await purple_api_controller.clients[user_pubkey_1].get_account();
100+
t.same(account_info_response_2.statusCode, 200);
101+
t.same(account_info_response_2.body.active, false);
102+
103+
let session_token = await purple_api_controller.login(user_pubkey_1);
104+
105+
// Now try to get notedeck instructions
106+
const notedeck_install_response = await purple_api_controller.clients[user_pubkey_1].get_notedeck_install_instructions({ session_token: session_token })
107+
t.same(notedeck_install_response.statusCode, 200);
108+
t.same(notedeck_install_response.body.value, get_notedeck_instructions(purple_api_controller, FREE_INSTRUCTIONS));
109+
110+
t.end();
111+
});

test/router_config.test.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ test('config_router - Account management routes', async (t) => {
6767
require_web_auth: async (req, res, next) => {
6868
req.authorized_pubkey = 'abc123';
6969
next();
70+
},
71+
use_web_auth: async (req, res, next) => {
72+
req.authorized_pubkey = 'abc123';
73+
next();
7074
}
7175
}
7276
};

0 commit comments

Comments
 (0)