Skip to content

Commit 2187e44

Browse files
committed
fix auth malformed token (#1102)
* add tests * allow legacy tokens with no perm claims * fmt
1 parent 594b1ce commit 2187e44

6 files changed

Lines changed: 122 additions & 42 deletions

File tree

libsql-server/src/auth/authorized.rs

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
use std::sync::Arc;
2+
13
use hashbrown::HashSet;
24

35
use crate::namespace::NamespaceName;
46

5-
use super::Permission;
7+
use super::{AuthError, Authenticated, Permission};
68

79
#[derive(Debug, serde::Deserialize, serde::Serialize, Default)]
810
pub struct Authorized {
@@ -30,17 +32,52 @@ impl Authorized {
3032
}
3133
}
3234

33-
pub fn merge_legacy(&mut self, namespace: NamespaceName, perm: Permission) {
34-
let scope = match perm {
35-
Permission::Read => self.read_only.get_or_insert_with(Default::default),
36-
Permission::Write => self.read_write.get_or_insert_with(Default::default),
37-
Permission::AttachRead => self.read_only_attach.get_or_insert_with(Default::default),
38-
};
39-
40-
scope
41-
.namespaces
42-
.get_or_insert_with(Default::default)
43-
.insert(namespace);
35+
fn is_empty(&self) -> bool {
36+
self.read_write.is_none()
37+
&& self.read_only.is_none()
38+
&& self.read_only_attach.is_none()
39+
&& self.read_write_attach.is_none()
40+
}
41+
42+
pub fn merge_legacy(
43+
mut self,
44+
namespace: Option<NamespaceName>,
45+
perm: Option<Permission>,
46+
) -> Result<Authenticated, AuthError> {
47+
match (namespace, perm) {
48+
(Some(ns), Some(perm)) => {
49+
let scope = match perm {
50+
Permission::Read => self.read_only.get_or_insert_with(Default::default),
51+
Permission::Write => self.read_write.get_or_insert_with(Default::default),
52+
Permission::AttachRead => {
53+
self.read_only_attach.get_or_insert_with(Default::default)
54+
}
55+
};
56+
scope
57+
.namespaces
58+
.get_or_insert_with(Default::default)
59+
.insert(ns);
60+
Ok(Authenticated::Authorized(Arc::new(self)))
61+
}
62+
// legacy shit: interpret that as full access to ns
63+
(Some(ns), None) => {
64+
self.read_write
65+
.get_or_insert_with(Default::default)
66+
.namespaces
67+
.get_or_insert_with(Default::default)
68+
.insert(ns);
69+
Ok(Authenticated::Authorized(Arc::new(self)))
70+
}
71+
(None, None) => {
72+
// if there are no other claims, no claims is interpreted as full access.
73+
if self.is_empty() {
74+
Ok(Authenticated::FullAccess)
75+
} else {
76+
Ok(Authenticated::Authorized(Arc::new(self)))
77+
}
78+
}
79+
_ => Err(AuthError::JwtInvalid),
80+
}
4481
}
4582

4683
fn can_write_ns(&self, name: &NamespaceName) -> bool {

libsql-server/src/auth/user_auth_strategies/jwt.rs

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -75,18 +75,8 @@ fn validate_jwt(
7575
match jsonwebtoken::decode::<Token>(jwt, jwt_key, &validation).map(|t| t.claims) {
7676
Ok(Token { id, a, p, .. }) => {
7777
// This is legacy: when nothing is specified, then it's full access
78-
if p.is_none() && id.is_none() && a.is_none() {
79-
return Ok(Authenticated::FullAccess);
80-
} else {
81-
let mut auth = p.unwrap_or_default();
82-
83-
// We only allow tokens if they contains a ns and a perm
84-
if let Some((ns, a)) = id.zip(a) {
85-
auth.merge_legacy(ns, a);
86-
}
87-
88-
Ok(Authenticated::Authorized(auth.into()))
89-
}
78+
let auth = p.unwrap_or_default();
79+
auth.merge_legacy(id, a)
9080
}
9181
Err(error) => Err(match error.kind() {
9282
ErrorKind::InvalidToken

libsql-server/tests/standalone/attach.rs

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,13 @@
1-
use base64::Engine;
21
use insta::assert_debug_snapshot;
3-
use jsonwebtoken::EncodingKey;
42
use libsql::Database;
5-
use ring::signature::{Ed25519KeyPair, KeyPair};
63

7-
use crate::common::{http::Client, net::TurmoilConnector};
4+
use crate::{
5+
common::{http::Client, net::TurmoilConnector},
6+
standalone::utils::{encode, key_pair},
7+
};
88

99
use super::make_standalone_server;
1010

11-
fn key_pair() -> (EncodingKey, Ed25519KeyPair) {
12-
let doc = Ed25519KeyPair::generate_pkcs8(&ring::rand::SystemRandom::new()).unwrap();
13-
let encoding_key = EncodingKey::from_ed_der(doc.as_ref());
14-
let pair = Ed25519KeyPair::from_pkcs8(doc.as_ref()).unwrap();
15-
(encoding_key, pair)
16-
}
17-
18-
fn encode<T: serde::Serialize>(claims: &T, key: &EncodingKey) -> String {
19-
let header = jsonwebtoken::Header::new(jsonwebtoken::Algorithm::EdDSA);
20-
jsonwebtoken::encode(&header, &claims, key).unwrap()
21-
}
22-
2311
#[test]
2412
fn attach_no_auth() {
2513
let mut sim = turmoil::Builder::new().build();
@@ -92,9 +80,8 @@ fn attach_auth() {
9280
sim.client("test", async {
9381
let client = Client::new();
9482

95-
let (enc, pair) = key_pair();
83+
let (enc, jwt_key) = key_pair();
9684

97-
let jwt_key = base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(pair.public_key().as_ref());
9885
assert!(client
9986
.post(
10087
"http://primary:9090/v1/namespaces/foo/create",
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use libsql::Database;
2+
3+
use crate::{
4+
common::{http::Client, net::TurmoilConnector},
5+
standalone::utils::{encode, key_pair},
6+
};
7+
8+
use super::make_standalone_server;
9+
10+
#[test]
11+
fn jwt_auth_namespace_access() {
12+
let mut sim = turmoil::Builder::new().build();
13+
14+
sim.host("primary", make_standalone_server);
15+
16+
sim.client("test", async {
17+
let client = Client::new();
18+
19+
let (enc, jwt_key) = key_pair();
20+
21+
assert!(client
22+
.post(
23+
"http://primary:9090/v1/namespaces/foo/create",
24+
serde_json::json!({ "jwt_key": jwt_key })
25+
)
26+
.await
27+
.unwrap()
28+
.status()
29+
.is_success());
30+
31+
let claims = serde_json::json!({
32+
"id": "foo",
33+
});
34+
let token = encode(&claims, &enc);
35+
36+
let foo_db = Database::open_remote_with_connector(
37+
"http://foo.primary:8080",
38+
&token,
39+
TurmoilConnector,
40+
)?;
41+
let foo_conn = foo_db.connect().unwrap();
42+
foo_conn.execute("SELECT 1", ()).await.unwrap();
43+
44+
Ok(())
45+
});
46+
47+
sim.run().unwrap();
48+
}

libsql-server/tests/standalone/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ use libsql_server::config::{AdminApiConfig, UserApiConfig};
1818
use common::net::{init_tracing, TestServer, TurmoilConnector};
1919

2020
mod attach;
21+
mod auth;
22+
mod utils;
2123

2224
async fn make_standalone_server() -> Result<(), Box<dyn std::error::Error>> {
2325
init_tracing();
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use base64::Engine;
2+
use jsonwebtoken::EncodingKey;
3+
use ring::signature::{Ed25519KeyPair, KeyPair};
4+
5+
pub fn key_pair() -> (EncodingKey, String) {
6+
let doc = Ed25519KeyPair::generate_pkcs8(&ring::rand::SystemRandom::new()).unwrap();
7+
let encoding_key = EncodingKey::from_ed_der(doc.as_ref());
8+
let pair = Ed25519KeyPair::from_pkcs8(doc.as_ref()).unwrap();
9+
let jwt_key = base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(pair.public_key().as_ref());
10+
(encoding_key, jwt_key)
11+
}
12+
13+
pub fn encode<T: serde::Serialize>(claims: &T, key: &EncodingKey) -> String {
14+
let header = jsonwebtoken::Header::new(jsonwebtoken::Algorithm::EdDSA);
15+
jsonwebtoken::encode(&header, &claims, key).unwrap()
16+
}

0 commit comments

Comments
 (0)