Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: homeserver signup tokens #74

Open
18 tasks
SHAcollision opened this issue Feb 20, 2025 · 4 comments · May be fixed by #80
Open
18 tasks

feat: homeserver signup tokens #74

SHAcollision opened this issue Feb 20, 2025 · 4 comments · May be fixed by #80
Labels
authn Authentication to the homeserver enhancement New feature or request

Comments

@SHAcollision
Copy link
Contributor

SHAcollision commented Feb 20, 2025

There is 2 features here: 1) admin endpoints, and 2) signup tokens. Luckily for us: we can use admin endpoints to create new signup tokens. And we can use a special signup token, to create an admin. BOOOOOOM 🤯

  1. Signup Tokens:

    • After the initial admin is created, new signup tokens can be generated through admin endpoint /generate_tokens.
    • These tokens can potentially be time-limited, one-time use, as needed.
  2. Admin/Master Signup Token:

    • Defined in config.toml and used only during initial setup.
    • The first user who registers using this token will gain Admin privileges.
    • This approach allows for secure bootstrap of the first admin user without pre-existing endpoints.
    • This willl allow us to reuse all existing pubky id tooling for admins as well. An admin is just a pubky user with a different role.

Implementation Plan Proposal

I've been looking in detail for every step involved into implementation of this feature. Of course, every plan is to be broken and improved as we go :) Consider this a "mental dry run" of how to go about it:

Admin Infrastructure

  • Extend the user schema to support roles. For example, we could add a simple admin: bool field.
  • Implement admin authentication middleware: Create a helper/middleware that validates a session’s token for admin privileges.
  • Reserve a new API route prefix (e.g. /admin) for admin endpoints.

Add a "master" signup token
Users who do signup with the "master" signup token, will get the admin: bool true .

  • Update config.toml and the configuration parser to add an optional field master_signup_token. Extend the Config struct to include this master token.

Signup Tokens Table

  • Define a new LMDB table (e.g. signup_tokens). Design the schema with fields:
    • token: String
    • created_at: u64
      - expires_at: Option<u64> (just an idea: not needed just yet, we don't need codes to expire for now!)
    • used: Option<PublicKey> (if None, token is unused; if Some(user_id), record the consuming user). Advanced: could (should?) also be a vector of users, just in case we want to allow multiple signups with a token (e.g., we need it if we want several admins with the same master_signup_token)
  • Implement serialization and deserialization for the signup token record.
  • Update the migration scripts to create this table.

Modify the Signup Endpoint

  • Update the client API signature to include a signup_token query parameter.
  • In the signup handler:
  1. Extract the signup_token from query parameters.
  2. Master Token Check:
    2.1. If the provided token equals the configured master token, proceed and mark the new user as admin.
  3. Regular Token Check:
    3.1. If not master, look up the token in the signup_tokens table.
    3.2. Verify that the token exists, is not expired, and is unused.
    3.3. Mark the token as used by storing the consuming user’s public key.
  4. Continue with the existing AuthToken verification and session creation.
  5. Update the user record creation to store the proper role (admin vs. regular).

Admin Endpoints for Signup Token Management

  • Implement an endpoint (e.g. POST /admin/generate_signup_token):
    • Secure this endpoint using the admin middleware.
    • Generate a secure random token (e.g. 16 random bytes, BASE32 encoded, all caps, possibly add hyphens for readability, would look like JBSW-Y3DP-EHPK-3PXQ-XR6M-7N2F-LM this).
    • Create a signup token record with the current timestamp and (optionally) an expiration.
    • Save the new token to the signup_tokens table and return the token.

Advanced: optionally in the future add endpoints for listing and revoking tokens. These would be useful for the Homeserver Dashboard.

  • List Endpoint: Return a list of tokens with metadata (creation time, usage, expiration).
  • Revoke Endpoint: Allow an admin to mark a token as revoked or delete it from the table.

Pubky Client

  • Make sure the Pubky Client is updated with the new param of client.signup(... signup_token)
  • Add a new method for admins client.gen_signup_token() that returns a new valid signup token.
  • Implement tests and release for native and wasm

Optional Signup Tokens

  • Please, allow a homeserver to be created and ran without requiring signup tokens. The client will submit a None instead. This will be tremendously useful, so testing does not become cumbersome. Every single test creates a homeserver, we don't want to always need to signup a admin, generate a token, etc in all testing workflows. We could also make it so testnet homeserver are always codeless/unprotected by default.
@SHAcollision SHAcollision added enhancement New feature or request authn Authentication to the homeserver labels Feb 20, 2025
@SeverinAlexB
Copy link
Collaborator

Some thoughts. I like the general direction but I think we can make it better. Let me know what you think.

Admin User

Why not simplify it and just set a admin password in LMDB? Single admin user either with a default password or a password that can be set with the cli that the user can change after the first signin. This is easier to program. Most applications like LND have this. Multi-user is a lot more effort.

Signup tokens

It is important to know what type of tokens we want. You describe onetime use here. If we want to go for a user attached tokens, the system and database models would look very different (see Bluesky summary here).

As far as I see, we need onetime tokens for the invite only beta and might want Bluesky style invite codes after. I personally don't see Bluesky style invite codes as Sybil prevention so that's another conversation we should have.

Likely best to consult with @BitcoinErrorLog here.

Signup Mode

Please, allow a homeserver to be created and ran without requiring signup tokens.

What about we create a config called signup_mode in config.toml?

  • open - Everybody can signup without restrictions. Set by default.
  • tokens_only - Token required.

Token Format

Generate a secure random token (e.g. 16 random bytes, BASE32 encoded, all caps, possibly add hyphens for readability, would look like JBSW-Y3DP-EHPK-3PXQ-XR6M-7N2F-LM this).

I don't think we need to go that hard. Shorter tokens should be sufficient and are way easier to handle. Think about the conference use case: A person gets a small card with a token printed on it which then needs to type in the signup form on his phone. JBSW-Y3DP-EHPK-3PXQ-XR6M-7N2F-LM is very hard to type. JBSW-Y3DP-EHPK is easier and with strict ip rate limiting on the signup endpoint (20 failed tries per minute?), it should be secure enough.

@SHAcollision
Copy link
Contributor Author

SHAcollision commented Feb 21, 2025

Why not simplify it and just set a admin password in LMDB?

Agree, we can make this simpler. The idea above tried to reuse our existing pubky_ids for admins as well. But in the spirit of KISS we can protect the new endpoints with a password directly coming from the config.toml , admin_password . Any request coming to the /admin that matches that password, can perform admin actions.

Is very hard to type. JBSW-Y3DP-EHPK is easier

Yes! Much better.

invite codes as Sybil prevention so that's another conversation we should have.

This gets difficult quickly and we risk over-complicating. I think for now it's safe to stick to "you need a signup token generated by the homeserver admin" . Always by admin, and how you get it is out of question. This is how most homeservers will likely operate anyway (single user, or a user and family/friends).

If we, on our own hosted homeserver, want users to be able to invite other users there is many ways to implement such feature by reusing "only tokens by admin". E.g., the homeserver runner could private message in /pubky-app all active users within some parameters ("background check of spam, quota, whaever") every 2 weeks a new invitation code, etc. Lot of room for design. Receiving invitation codes on a DM from admin once a year and stuff like that was popular back in times of "web 1" forums 😄

What about we create a config called signup_mode in config.toml?

Yeah, I think this is good! It's also OK if this config is not even exposed on config.toml at all. Just a new field open_signup: bool that defaults to false (might need to be part of IoConfig)

pub struct Config {
/// Server keypair.
///
/// Defaults to a random keypair.
pub keypair: Keypair,
pub io: IoConfig,
pub core: CoreConfig,
}

pub struct IoConfig {
pub http_port: u16,
pub https_port: u16,
pub public_addr: Option<SocketAddr>,
pub domain: Option<String>,
/// Bootstrapping DHT nodes.
///
/// Helpful to run the server locally or in testnet.
pub bootstrap: Option<Vec<String>>,
pub dht_request_timeout: Option<Duration>,
}

And we set it to true only on the run_test()

pub fn test(bootstrap: &[String]) -> Self {
let bootstrap = Some(bootstrap.to_vec());
Self {
io: IoConfig {
bootstrap,
http_port: 0,
https_port: 0,
..Default::default()
},

pub async fn run_test(bootstrap: &[String]) -> Result<Self> {
let config = Config::test(bootstrap);
unsafe { Self::run(config) }.await
}

@SeverinAlexB
Copy link
Collaborator

Sounds good.

About signup tokens and nexus:

In theory, tokens should give you access to the homeserver and nexus. This implies some coordination though because both the homeserver and nexus require the same token. To solve this problem, I propose the following:

  • Only the homeserver issues tokens and the users signs up with this token to the home server only.
  • Nexus only reads from our home server and refuses to read from any other homeserver. This way, Nexus doesn't need it's own token system.

Maybe I am just repeating what you guys were already planning but I think this is the most sensible and simplest approach. What do you think?

@SHAcollision
Copy link
Contributor Author

  • Nexus only reads from our home server and refuses to read from any other homeserver. This way, Nexus doesn't need it's own token system.

This is it. For now Nexus can only track one homeserver. Different spam protection mechanisms are envisioned for future multi-homeserver. But in no case an auth token is needed 👌

@SHAcollision SHAcollision linked a pull request Feb 26, 2025 that will close this issue
9 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
authn Authentication to the homeserver enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants