Skip to content

feat: add generic short code authentication system#3078

Draft
apophisnow wants to merge 8 commits intomusic-assistant:devfrom
apophisnow:feature/generic-short-code-auth
Draft

feat: add generic short code authentication system#3078
apophisnow wants to merge 8 commits intomusic-assistant:devfrom
apophisnow:feature/generic-short-code-auth

Conversation

@apophisnow
Copy link
Contributor

@apophisnow apophisnow commented Feb 2, 2026

Add a reusable short code authentication system that any provider can use for QR code login, device pairing, or similar flows.

Changes:

  • Add join_codes database table
  • Add generate_join_code(user_id, provider_name, ...) method
  • Add exchange_join_code() to convert codes to JWT tokens
  • Add auth/code public API endpoint
  • Add revoke_join_codes() for cleanup
  • Update login.html to handle ?join= parameter
  • Add provider_name parameter to JWT token encoding

Providers can implement short code auth flows like:

code, expires = await auth.generate_join_code(
    user_id=my_user.user_id,
    provider_name="my_provider",
    expires_in_hours=24,
)

The provider_name is stored in the join code and passed to the JWT token, allowing providers to identify their authenticated sessions.

apophisnow and others added 2 commits February 1, 2026 21:02
Add a reusable short code authentication system that any provider can use
for QR code login, device pairing, or similar flows.

Changes:
- Add join_codes database table (schema v6)
- Add generate_join_code(user_id, provider_name, ...) method
- Add exchange_join_code() to convert codes to JWT tokens
- Add auth/code public API endpoint
- Add revoke_join_codes() for cleanup
- Update login.html to handle ?join= parameter
- Add provider_name parameter to JWT token encoding

Providers can implement short code auth flows like:

    code, expires = await auth.generate_join_code(
        user_id=my_user.user_id,
        provider_name="my_provider",
        expires_in_hours=24,
    )

The provider_name is stored in the join code and passed to the JWT token,
allowing providers to identify their authenticated sessions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@apophisnow apophisnow marked this pull request as ready for review February 5, 2026 09:41
@MarvinSchenkel
Copy link
Contributor

Could you please update the auth tests to also include these new short code cases?

apophisnow and others added 2 commits February 6, 2026 10:56
Add tests covering the short code authentication system:
- generate_join_code: basic functionality, invalid user handling
- exchange_join_code: success, case-insensitivity, expired codes,
  max_uses limits, unlimited uses, provider_name in JWT claims
- revoke_join_codes: per-user revocation, revoke all codes
- authenticate_with_join_code API: success and error cases

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@apophisnow
Copy link
Contributor Author

Could you please update the auth tests to also include these new short code cases?

Added tests.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a reusable “join code” (short code) authentication flow that can be used by providers for QR-code login/device pairing: generates short codes tied to users, exchanges them for JWTs (including provider_name claim), exposes a public auth/code API command, and updates the login page to accept ?join=.

Changes:

  • Add join_codes table + DB schema bump/migration and supporting CRUD in AuthenticationManager.
  • Add join-code exchange API command (auth/code) and JWT claim support (provider_name).
  • Update login.html to auto-authenticate when a join query parameter is present; add tests for the new flow.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.

File Description
music_assistant/controllers/webserver/auth.py Implements join code generation/exchange/revocation, DB schema v5 migration, and public auth/code command.
music_assistant/helpers/jwt_auth.py Adds optional provider_name claim to JWT encoding.
music_assistant/helpers/resources/login.html Adds client-side join-code handling via JSON-RPC call to auth/code.
tests/test_webserver_auth.py Adds test coverage for join code generation, exchange, expiry, max uses, revocation, and API command behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1560 to +1567
async def generate_join_code(
self,
user_id: str,
expires_in_hours: int = JOIN_CODE_DEFAULT_EXPIRY_HOURS,
max_uses: int = 0,
device_name: str = "Short Code Login",
provider_name: str | None = None,
) -> tuple[str, datetime]:
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generate_join_code accepts expires_in_hours and max_uses without validation. Negative values currently create already-expired codes or effectively unlimited-use codes (when max_uses is negative). Add input validation to reject negatives (and potentially enforce reasonable upper bounds) so providers can’t accidentally create insecure/invalid codes.

Copilot uses AI. Check for mistakes.
Comment on lines 1634 to 1638
# Check use limit
max_uses = code_row["max_uses"]
use_count = code_row["use_count"]
if max_uses > 0 and use_count >= max_uses:
return None
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When max_uses is exceeded, exchange_join_code returns None but leaves the exhausted code row in join_codes. This can cause unbounded table growth and keeps “dead” codes around indefinitely. Consider deleting the row once it reaches the use limit (and/or adding periodic cleanup of expired/exhausted rows).

Copilot uses AI. Check for mistakes.
Comment on lines +1586 to +1605
# Generate short alphanumeric code
code = "".join(secrets.choice(JOIN_CODE_CHARSET) for _ in range(JOIN_CODE_LENGTH))
code_hash = hashlib.sha256(code.encode()).hexdigest()

now = utc()
expires_at = now + timedelta(hours=expires_in_hours)

code_data = {
"code_id": secrets.token_urlsafe(32),
"code_hash": code_hash,
"user_id": user_id,
"created_at": now.isoformat(),
"expires_at": expires_at.isoformat(),
"max_uses": max_uses,
"use_count": 0,
"device_name": device_name,
"provider_name": provider_name,
}
await self.database.insert("join_codes", code_data)
await self.database.commit()
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generate_join_code inserts code_hash with a UNIQUE constraint but does not handle collisions. With 6-char codes, a rare collision will raise an IntegrityError and fail the flow. Consider retrying code generation on UNIQUE-constraint failure (bounded attempts) before giving up with a clear error.

Copilot uses AI. Check for mistakes.
@marcelveldt
Copy link
Member

marcelveldt commented Feb 7, 2026

Can you please walk me through the entire flow here you envision for the guest role + party mode ?

So, you create a user with GUEST role and then you can optionally request a short code token ?
How would a party mode guest user log in ? Remote access ?
Is there a single short code / join code for the whole "party" ? So all your guests share the same join code (and thus MA guest account) ?

At least, I think we should limit this feature to guest accounts only, for the sake of security.
Otherwise it looks fine, just some small feedback below:

Security enhancements:

  • Default max_uses=1 instead of unlimited (safer default)
  • Add automatic cleanup of expired codes (scheduled task)
  • Log security events (code generation, exchange, rejection)

API improvements:

  • Consider renaming to guest_access_code or quick_login_code to clarify purpose
  • Add API endpoint to list active codes for a user (for management UI)
  • Add ability to revoke a specific code (not just all codes for a user)

@apophisnow
Copy link
Contributor Author

Can you please walk me through the entire flow here you envision for the guest role + party mode ?

So, you create a user with GUEST role and then you can optionally request a short code token ? How would a party mode guest user log in ? Remote access ? Is there a single short code / join code for the whole "party" ? So all your guests share the same join code (and thus MA guest account) ?

At least, I think we should limit this feature to guest accounts only, for the sake of security. Otherwise it looks fine, just some small feedback below:

Security enhancements:

  • Default max_uses=1 instead of unlimited (safer default)
  • Add automatic cleanup of expired codes (scheduled task)
  • Log security events (code generation, exchange, rejection)

API improvements:

  • Consider renaming to guest_access_code or quick_login_code to clarify purpose
  • Add API endpoint to list active codes for a user (for management UI)
  • Add ability to revoke a specific code (not just all codes for a user)

Sure thing. This specific PR is just to provide a mechanism for short join URLs which allow access without requiring manual login. So a URL like http://localhost:8095/?join=6Y6XQC can be provided to a user (through a text message, QR code, or any other means) which will allow them access without creating an account and logging in.

For party mode the idea is:
Enable party mode, it creates a party mode guest account with limited access, and a short join code which is used to generate the QR code. This all happens automatically when you enable party mode (not on an optional basis). It is a single account and join code (though each user will create a new session).

Since the join code is short it makes the QR code easier to scan, and if needed the URL could be easily shared with guests by writing it down, through text message, or even reading it aloud.

I think your suggestions make sense, i'll work to implement them.

@marcelveldt marcelveldt marked this pull request as draft February 8, 2026 18:13
@marcelveldt marcelveldt marked this pull request as draft February 8, 2026 18:13
@marcelveldt
Copy link
Member

Marked it as draft - please hit the "ready for review" button once you have implemented the suggestions. Thanks!

apophisnow and others added 4 commits February 17, 2026 18:26
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants