Access control
Access control decides who may join a server. The server checks it every time someone authenticates, so a client needs to know about it to join a server that isn’t open.
A server is in one of five access modes:
| Mode | Who gets in |
|---|---|
open |
Anyone who authenticates. This is the default. |
whitelist |
Whitelisted users and existing members. |
password |
Whitelisted users, and anyone who sends the server password. |
application |
Whitelisted users and existing members. Others apply and wait for a reviewer. |
invite |
Whitelisted users, and anyone who sends a usable invite code. |
A member is a user whose account on this server is marked as joined. Someone becomes a member the first time they authenticate successfully, and stops being one when they leave. The server owner is always let in, whatever the mode.
How the server checks access
Section titled “How the server checks access”The check runs inside auth, login, and register. It runs after the ban check and before the member limit check.
Each of those commands takes two optional fields for access control:
{ "cmd": "login", "username": "alice", "password": "<account password>", "server_password": "<server password>", "invite_code": "3F9A1C7E52B4"}| Field | Type | Description |
|---|---|---|
server_password |
string | The server password. Used only in password mode. |
invite_code |
string | An invite code. Used only in invite mode. The server trims it and uppercases it. |
When access is refused, authentication fails with an auth_error frame that carries a machine-readable reason and the current mode:
{ "cmd": "auth_error", "val": "Your join application is pending review", "reason": "application_pending", "mode": "application", "application_id": "5b1f3c1e-8d0a-4d6e-9f43-2a7c9e1b0d55"}reason |
val |
Mode |
|---|---|---|
not_whitelisted |
You are not whitelisted on this server |
whitelist |
password_required |
This server requires a password to join |
password |
password_incorrect |
Incorrect server password |
password |
application_required |
This server requires approval to join. Submit an application first. |
application |
application_pending |
Your join application is pending review |
application |
invite_required |
This server requires an invite code to join |
invite |
invite_invalid |
Invalid invite code |
invite |
invite_expired |
This invite has expired |
invite |
application_id appears only with application_pending. The connection stays open after an auth_error, so the client can retry authentication on it.
Who skips which check
Section titled “Who skips which check”| User | whitelist |
password |
application |
invite |
|---|---|---|---|---|
| Server owner | Admitted | Admitted | Admitted | Admitted |
| Whitelisted user | Admitted | Admitted | Admitted | Admitted |
| Existing member | Admitted | Needs the password | Admitted | Needs an invite |
| Anyone else | Refused | Needs the password | Must apply | Needs an invite |
Learn the mode before authenticating
Section titled “Learn the mode before authenticating”The handshake frame includes the public access settings, so a client can prompt for a password or invite code before it authenticates:
{ "cmd": "handshake", "val": { "access_control": { "mode": "password", "has_password": true } }}(Other handshake fields are left out here.) After joining, members can read the same data with access_mode_get.
Joining a server in each mode
Section titled “Joining a server in each mode”Open. Authenticate normally.
Whitelist. Authenticate normally. If the user isn’t whitelisted or a member, the server returns not_whitelisted. Someone with manage_access must add them with whitelist_add. The user can also send application_submit, which the server accepts in any mode. An approved application adds the user to the whitelist.
Password.
- Read
access_control.modefromhandshake. If it’spassword, ask the user for the server password. - Authenticate with
server_passwordset. - On
password_incorrect, ask again and retry.
Application.
- Authenticate. A new user gets
application_required. - Send
application_submitwithuser_id,username, and an optional message. The refused connection has no identity, so the server can’t fill these in. The response hasstatus: "pending". - Wait for a reviewer. Retrying authentication meanwhile returns
application_pending. - When a reviewer approves, the user is added to the whitelist. Authenticate again to join. The server sends
application_approvedonly to connections already authenticated as the applicant, so a waiting client should retry on its own rather than wait for that frame.
Invite.
- Get a code, usually from an invite link such as
https://chat.example.com/invite/3F9A1C7E52B4. - Optionally call
GET /invite/{code}/checkto show the server’s name and check that the code works. See Invite pages. - Authenticate with
invite_codeset. The server spends one use of the invite and adds the user to the whitelist. - Later logins don’t need the code.
Access modes
Section titled “Access modes”Read the mode with access_mode_get and change it with access_mode_set. A change applies to the next authentication. Users already connected stay connected.
When the mode changes, every other authenticated connection receives:
{ "cmd": "access_mode_update", "mode": "invite" }The server stores access data in access.json in its database directory. The access_control.mode config value seeds the mode only when that file doesn’t exist yet. After that, access.json is the source of truth.
Whitelist
Section titled “Whitelist”The whitelist is a list of user IDs that are always let in, whatever the mode.
whitelist_addadds a user by username or ID. The server must already know the user, which means they have authenticated here before.whitelist_removeremoves a user. It doesn’t remove their membership or disconnect them.whitelist_listlists every entry.
The server also adds users to the whitelist when a reviewer approves their application and when they redeem an invite.
Server password
Section titled “Server password”server_password_set sets or removes the password. The server stores only a bcrypt hash. Passwords are 1 to 72 characters.
passwordmode needs a password.access_mode_setrefusespassworduntil one is set.- Removing the password while in
passwordmode switches the server toopen. - A password can be set in any mode, but the server checks it only in
passwordmode.
Applications
Section titled “Applications”An application is a request to join, reviewed by a moderator. It looks like this:
{ "id": "5b1f3c1e-8d0a-4d6e-9f43-2a7c9e1b0d55", "user_id": "USR:local_alice", "username": "USR:local_alice", "message": "Friend of Bob's, here for the game nights.", "status": "pending", "created_at": 1767225600.123, "reviewed_at": null, "reviewed_by": null}| Field | Description |
|---|---|
id |
Application ID, a UUID. |
user_id, username |
The applicant. |
message |
The applicant’s message, up to 500 characters. May be empty. |
status |
pending, approved, or rejected. |
created_at |
When it was submitted, in Unix seconds. |
reviewed_at, reviewed_by |
When and by whom it was reviewed. null while pending. reviewed_by is a user ID. |
An application moves through these states:
application_submitcreates it aspending. A user has at most one pending application; submitting again returns the existing one.application_reviewmoves it toapprovedorrejected. Only pending applications can be reviewed. Approval adds the user to the whitelist.- A rejected user may submit a new application. An approved user can’t, because they are already whitelisted.
application_list lists applications, optionally by status. application_delete removes one from the record. Deleting an approved application doesn’t remove the user from the whitelist. Applications are never removed automatically.
When a pending application is submitted, every authenticated connection with manage_access or review_applications receives:
{ "cmd": "application_new", "application": { "id": "5b1f3c1e-8d0a-4d6e-9f43-2a7c9e1b0d55", "user_id": "USR:local_alice", "username": "USR:local_alice", "message": "Friend of Bob's, here for the game nights.", "status": "pending", "created_at": 1767225600.123, "reviewed_at": null, "reviewed_by": null }, "pending": 3}pending is the number of pending applications.
Invite codes
Section titled “Invite codes”An invite is a code that lets someone join in invite mode.
{ "code": "3F9A1C7E52B4", "max_uses": 5, "uses": 1, "created_by": "USR:local_bob", "created_at": 1767225600.123, "expires_at": 1767830400.123}| Field | Description |
|---|---|
code |
12 uppercase hexadecimal characters. |
max_uses |
How many people can redeem it. 0 means unlimited. |
uses |
How many times it has been redeemed. |
created_by |
User ID of the creator. |
created_at |
Unix seconds. |
expires_at |
Unix seconds, or null if it never expires. |
An invite is usable while it hasn’t expired and uses is below max_uses (or max_uses is 0).
invite_createmakes an invite.max_usesdefaults to 1 and can be up to 1000.expires_inis optional and can be up to 30 days.invite_listlists every invite, including expired and used-up ones.invite_deletedeletes one. Spent and expired invites stay until someone deletes them.
Invites can be created in any mode, but the server accepts invite_code only in invite mode. A whitelisted user who sends a code doesn’t use it up.
Invite pages
Section titled “Invite pages”The server has two public HTTP routes for invite links.
GET /invite/{code} redirects with 302 to https://originchats.com/invite/{host}/{code}, where {host} is the host of the server’s configured server.url. It doesn’t check the code. It returns 404 with {"error": "Invite not found"} if the code is blank or the server has no URL configured.
GET /invite/{code}/check checks a code without spending a use. It always returns the server’s public info in server.
{ "valid": true, "code": "3F9A1C7E52B4", "expires_at": 1767830400.123, "server": { "name": "Game Night", "icon": "https://chat.example.com/server-assets/icon.png", "banner": "", "url": "https://chat.example.com", "owner": { "name": "bob" } }}An unusable code returns 200 with valid: false:
{ "valid": false, "reason": "invite_expired", "server": { "name": "Game Night", "icon": "", "banner": "", "url": "https://chat.example.com", "owner": { "name": "bob" } }}reasonisinvite_expiredif the code exists but has expired, otherwiseinvite_invalid. A used-up code that hasn’t expired returnsinvite_invalid.- The check is case-sensitive. Send the code in uppercase. Authentication uppercases it for you; this route doesn’t.
- The response doesn’t say whether the server is in
invitemode. Read that fromhandshake.
Permissions
Section titled “Permissions”| Permission | Allows |
|---|---|
manage_access |
Everything on this page: changing the mode, managing the whitelist and password, listing and deleting invites, and reviewing and deleting applications. access_mode_get also returns extra counts. |
review_applications |
application_list and application_review, plus application_new events. |
create_invite |
invite_create. The default user role has this permission. |
The owner and any role with administrator have all three. See roles and permissions.
Other rules:
- Only
application_submitworks before a connection is admitted. Every other command here needs an authenticated member. - A user in timeout can’t change access settings, review or delete applications, or create or delete invites. They get a
rate_limitframe instead. Reads still work. See the protocol model. - Every change is written to the audit log under the
server_managementcategory. Removing the password is logged asserver_password_remove.
Commands
Section titled “Commands”| Command | Purpose |
|---|---|
access_mode_get |
Read the access mode. |
access_mode_set |
Change the access mode. |
whitelist_add |
Add a user to the whitelist. |
whitelist_remove |
Remove a user from the whitelist. |
whitelist_list |
List whitelisted users. |
server_password_set |
Set or remove the server password. |
application_submit |
Apply to join. |
application_list |
List applications. |
application_review |
Approve or reject an application. |
application_delete |
Delete or withdraw an application. |
invite_create |
Create an invite code. |
invite_list |
List invite codes. |
invite_delete |
Delete an invite code. |
