Skip to content

Commit ad80b6a

Browse files
feat!: Add OIDC login flow
BREAKING CHANGE: Previously available config options have been moved under the `jwt` key. See `README.md` for an example.
1 parent 41ac445 commit ad80b6a

File tree

8 files changed

+422
-94
lines changed

8 files changed

+422
-94
lines changed

README.md

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![PyPI - Version](https://img.shields.io/pypi/v/synapse-token-authenticator.svg)](https://pypi.org/project/synapse-token-authenticator)
44
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/synapse-token-authenticator.svg)](https://pypi.org/project/synapse-token-authenticator)
55

6-
Synapse Token Authenticator is a synapse auth provider which allows for token authentication (and optional registration) using JWTs (Json Web Tokens).
6+
Synapse Token Authenticator is a synapse auth provider which allows for token authentication (and optional registration) using JWTs (Json Web Tokens) and OIDC.
77

88
-----
99

@@ -25,20 +25,33 @@ pip install synapse-token-authenticator
2525
## Configuration
2626
Here are the available configuration options:
2727
```yaml
28-
# provide only one of secret, keyfile
29-
secret: symetrical secret
30-
keyfile: path to asymetrical keyfile
31-
32-
# Algorithm of the tokens, defaults to HS512
33-
#algorithm: HS512
34-
# Allow registration of new users using these tokens, defaults to false
35-
#allow_registration: false
36-
# Require tokens to have an expiry set, defaults to true
37-
#require_expiry: true
28+
jwt:
29+
# provide only one of secret, keyfile
30+
secret: symetrical secret
31+
keyfile: path to asymetrical keyfile
32+
33+
# Algorithm of the tokens, defaults to HS512 (optional)
34+
algorithm: HS512
35+
# Allow registration of new users, defaults to false (optional)
36+
allow_registration: false
37+
# Require tokens to have an expiry set, defaults to true (optional)
38+
require_expiry: true
39+
oidc:
40+
issuer: "https://idp.example.com"
41+
client_id: "<IDP client id>"
42+
client_secret: "<IDP client secret>"
43+
project_id: # TODO: improve docs
44+
organization_id: # TODO: improve docs
45+
# Limits access to specified clients. Allows any client if not set (optional)
46+
allowed_client_ids: ['foo', 'bar'] # TODO: better examples
47+
# Allow registration of new users, defaults to false (optional)
48+
allow_registration: false
3849
```
3950
It is recommended to have `require_expiry` set to `true` (default). As for `allow_registration`, it depends on usecase: If you only want to be able to log in *existing* users, leave it at `false` (default). If nonexistant users should be simply registered upon hitting the login endpoint, set it to `true`.
4051

4152
## Usage
53+
54+
### JWT Authentication
4255
First you have to generate a JWT with the correct claims. The `sub` claim is the localpart or full mxid of the user you want to log in as. Be sure that the algorithm and secret match those of the configuration. An example of the claims is as follows:
4356
```json
4457
{
@@ -59,6 +72,10 @@ Next you need to post this token to the `/login` endpoint of synapse. Be sure th
5972
}
6073
```
6174

75+
### OIDC Authentication
76+
77+
<!-- TODO: write a summary of the notion page here -->
78+
6279
## Testing
6380

6481
The tests uses twisted's testing framework trial, with the development

pyproject.toml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,13 @@ dependencies = [
3737
"pytest",
3838
"pytest-cov",
3939
"mock",
40-
"matrix-synapse"
40+
"matrix-synapse",
41+
"ruff",
4142
]
4243
[tool.hatch.envs.default.scripts]
4344
cov = "pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=synapse_token_authenticator --cov=tests"
4445
format = "black ."
46+
lint = "ruff check ."
4547

4648
[tool.hatch.envs.ci.scripts]
4749
format = "black --check ."
@@ -54,8 +56,4 @@ parallel = true
5456
omit = []
5557

5658
[tool.coverage.report]
57-
exclude_lines = [
58-
"no cov",
59-
"if __name__ == .__main__.:",
60-
"if TYPE_CHECKING:",
61-
]
59+
exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"]

synapse_token_authenticator/config.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import os
2+
3+
4+
class TokenAuthenticatorConfig:
5+
"""
6+
Parses and validates the provided config dictionary.
7+
"""
8+
9+
def __init__(self, other: dict):
10+
if jwt := other.get("jwt"):
11+
12+
class JwtConfig:
13+
def __init__(self, other: dict):
14+
self.secret: str | None = other.get("secret")
15+
self.keyfile: str | None = other.get("keyfile")
16+
17+
self.algorithm: str = other.get("algorithm", "HS512")
18+
self.allow_registration: bool = other.get(
19+
"allow_registration", False
20+
)
21+
self.require_expiry: bool = other.get("require_expiry", True)
22+
23+
self.jwt = JwtConfig(jwt)
24+
self.verify_jwt()
25+
26+
if oidc := other.get("oidc"):
27+
28+
class OIDCConfig:
29+
def __init__(self, other: dict):
30+
try:
31+
self.issuer: str = other["issuer"]
32+
self.client_id: str = other["client_id"]
33+
self.client_secret: str = other["client_secret"]
34+
self.project_id: str = other["project_id"]
35+
self.organization_id: str = other["organization_id"]
36+
except KeyError as error:
37+
raise Exception(f"Config option must be set: {error.args[0]}")
38+
39+
self.allowed_client_ids: str | None = other.get(
40+
"allowed_client_ids"
41+
)
42+
43+
self.allow_registration: bool = other.get(
44+
"allow_registration", False
45+
)
46+
47+
self.oidc = OIDCConfig(oidc)
48+
49+
def verify_jwt(self):
50+
if self.jwt.secret is None and self.jwt.keyfile is None:
51+
raise Exception("Missing secret or keyfile")
52+
if self.jwt.keyfile is not None and not os.path.exists(self.jwt.keyfile):
53+
raise Exception("Keyfile doesn't exist")
54+
55+
if self.jwt.algorithm not in [
56+
"HS256",
57+
"HS384",
58+
"HS512",
59+
"RS256",
60+
"RS384",
61+
"RS512",
62+
"ES256",
63+
"ES384",
64+
"ES512",
65+
"PS256",
66+
"PS384",
67+
"PS512",
68+
"EdDSA",
69+
]:
70+
raise Exception(f"Unknown algorithm: '{self.jwt.algorithm}'")

0 commit comments

Comments
 (0)