Skip to content
This repository was archived by the owner on Jan 14, 2022. It is now read-only.

Commit 1765dc3

Browse files
committed
Check oauth scopes and reject unauthorized requests
1 parent f8a82ca commit 1765dc3

2 files changed

Lines changed: 72 additions & 18 deletions

File tree

src/main.rs

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use receiver::Receiver;
4040
use std::env;
4141
use std::net::SocketAddr;
4242
use stream::StreamManager;
43-
use user::{Scope, User};
43+
use user::{OauthScope::*, Scope, User};
4444
use warp::path;
4545
use warp::Filter as WarpFilter;
4646

@@ -110,29 +110,49 @@ fn main() {
110110
h: query::Hashtag,
111111
l: query::List,
112112
ws: warp::ws::Ws2| {
113-
let unauthorized = Err(warp::reject::custom("Error: Invalid Access Token"));
113+
let scopes = user.scopes.clone();
114114
let timeline = match q.stream.as_ref() {
115115
// Public endpoints:
116116
tl @ "public" | tl @ "public:local" if m.is_truthy() => format!("{}:media", tl),
117117
tl @ "public:media" | tl @ "public:local:media" => tl.to_string(),
118118
tl @ "public" | tl @ "public:local" => tl.to_string(),
119-
// User
120-
"user" if user.id == -1 => return unauthorized,
121-
"user" => format!("{}", user.id),
122-
"user:notification" => {
123-
user = user.with_notification_filter();
124-
format!("{}", user.id)
125-
}
126119
// Hashtag endpoints:
127120
// TODO: handle missing query
128121
tl @ "hashtag" | tl @ "hashtag:local" => format!("{}:{}", tl, h.tag),
122+
// Private endpoints: User
123+
"user"
124+
if user.id > 0
125+
&& (scopes.contains(&Read) || scopes.contains(&ReadStatuses)) =>
126+
{
127+
format!("{}", user.id)
128+
}
129+
"user:notification"
130+
if user.id > 0
131+
&& (scopes.contains(&Read) || scopes.contains(&ReadNotifications)) =>
132+
{
133+
user = user.with_notification_filter();
134+
format!("{}", user.id)
135+
}
129136
// List endpoint:
130137
// TODO: handle missing query
131-
"list" if user.authorized_for_list(l.list).is_err() => return unauthorized,
132-
"list" => format!("list:{}", l.list),
138+
"list"
139+
if user.authorized_for_list(l.list).is_ok()
140+
&& (scopes.contains(&Read) || scopes.contains(&ReadList)) =>
141+
{
142+
format!("list:{}", l.list)
143+
}
144+
133145
// Direct endpoint:
134-
"direct" if user.id == -1 => return unauthorized,
135-
"direct" => "direct".to_string(),
146+
"direct"
147+
if user.id > 0
148+
&& (scopes.contains(&Read) || scopes.contains(&ReadStatuses)) =>
149+
{
150+
"direct".to_string()
151+
}
152+
// Reject unathorized access attempts for private endpoints
153+
"user" | "user:notification" | "direct" | "list" => {
154+
return Err(warp::reject::custom("Error: Invalid Access Token"))
155+
}
136156
// Other endpoints don't exist:
137157
_ => return Err(warp::reject::custom("Error: Nonexistent WebSocket query")),
138158
};

src/user.rs

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,36 +28,68 @@ pub enum Filter {
2828
pub struct User {
2929
pub id: i64,
3030
pub access_token: String,
31+
pub scopes: Vec<OauthScope>,
3132
pub langs: Option<Vec<String>>,
3233
pub logged_in: bool,
3334
pub filter: Filter,
3435
}
36+
#[derive(Clone, Debug, PartialEq)]
37+
pub enum OauthScope {
38+
Read,
39+
ReadStatuses,
40+
ReadNotifications,
41+
ReadList,
42+
Other,
43+
}
44+
impl From<&str> for OauthScope {
45+
fn from(scope: &str) -> Self {
46+
use OauthScope::*;
47+
match scope {
48+
"read" => Read,
49+
"read:statuses" => ReadStatuses,
50+
"read:notifications" => ReadNotifications,
51+
"read:lists" => ReadList,
52+
_ => Other,
53+
}
54+
}
55+
}
3556
impl User {
3657
/// Create a user from the access token supplied in the header or query paramaters
37-
pub fn from_access_token(token: String, scope: Scope) -> Result<Self, warp::reject::Rejection> {
58+
pub fn from_access_token(
59+
access_token: String,
60+
scope: Scope,
61+
) -> Result<Self, warp::reject::Rejection> {
3862
let conn = connect_to_postgres();
3963
let result = &conn
4064
.query(
4165
"
42-
SELECT oauth_access_tokens.resource_owner_id, users.account_id, users.chosen_languages
66+
SELECT oauth_access_tokens.resource_owner_id, users.account_id, users.chosen_languages, oauth_access_tokens.scopes
4367
FROM
4468
oauth_access_tokens
4569
INNER JOIN users ON
4670
oauth_access_tokens.resource_owner_id = users.id
4771
WHERE oauth_access_tokens.token = $1
4872
AND oauth_access_tokens.revoked_at IS NULL
4973
LIMIT 1",
50-
&[&token],
74+
&[&access_token],
5175
)
5276
.expect("Hard-coded query will return Some([0 or more rows])");
5377
if !result.is_empty() {
5478
let only_row = result.get(0);
5579
let id: i64 = only_row.get(1);
80+
let scopes = only_row
81+
.get::<_, String>(3)
82+
.split(' ')
83+
.map(|scope: &str| scope.into())
84+
.filter(|scope| scope != &OauthScope::Other)
85+
.collect();
86+
dbg!(&scopes);
5687
let langs: Option<Vec<String>> = only_row.get(2);
5788
info!("Granting logged-in access");
5889
Ok(User {
5990
id,
60-
access_token: token,
91+
access_token,
92+
scopes,
6193
langs,
6294
logged_in: true,
6395
filter: Filter::None,
@@ -66,7 +98,8 @@ LIMIT 1",
6698
info!("Granting public access to non-authenticated client");
6799
Ok(User {
68100
id: -1,
69-
access_token: token,
101+
access_token,
102+
scopes: Vec::new(),
70103
langs: None,
71104
logged_in: false,
72105
filter: Filter::None,
@@ -120,6 +153,7 @@ LIMIT 1",
120153
User {
121154
id: -1,
122155
access_token: String::new(),
156+
scopes: Vec::new(),
123157
langs: None,
124158
logged_in: false,
125159
filter: Filter::None,

0 commit comments

Comments
 (0)