OAuth 2.0 Authorization Server [MOD-559] (#733)

* WIP end-of-day push

* Authorize endpoint, accept endpoints, DB stuff for oauth clients, their redirects, and client authorizations

* OAuth Client create route

* Get user clients

* Client delete

* Edit oauth client

* Include redirects in edit client route

* Database stuff for tokens

* Reorg oauth stuff out of auth/flows and into its own module

* Impl OAuth get access token endpoint

* Accept oauth access tokens as auth and update through AuthQueue

* User OAuth authorization management routes

* Forgot to actually add the routes lol

* Bit o cleanup

* Happy path test for OAuth and minor fixes for things it found

* Add dummy data oauth client (and detect/handle dummy data version changes)

* More tests

* Another test

* More tests and reject endpoint

* Test oauth client and authorization management routes

* cargo sqlx prepare

* dead code warning

* Auto clippy fixes

* Uri refactoring

* minor name improvement

* Don't compile-time check the test sqlx queries

* Trying to fix db concurrency problem to get tests to pass

* Try fix from test PR

* Fixes for updated sqlx

* Prevent restricted scopes from being requested or issued

* Get OAuth client(s)

* Remove joined oauth client info from authorization returns

* Add default conversion to OAuthError::error so we can use ?

* Rework routes

* Consolidate scopes into SESSION_ACCESS

* Cargo sqlx prepare

* Parse to OAuthClientId automatically through serde and actix

* Cargo clippy

* Remove validation requiring 1 redirect URI on oauth client creation

* Use serde(flatten) on OAuthClientCreationResult
This commit is contained in:
Jackson Kruger
2023-10-30 11:14:38 -05:00
committed by GitHub
parent 8803e11945
commit 6cfd4637db
54 changed files with 3658 additions and 135 deletions

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE pats\n SET last_used = $2\n WHERE (id = $1)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Timestamptz"
]
},
"nullable": []
},
"hash": "0472045549758d8eef84592908c438d6222a26926f4b06865b84979fc92564ba"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE pats\n SET last_used = $2\n WHERE id IN\n (SELECT * FROM UNNEST($1::bigint[]))\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8Array",
"Timestamptz"
]
},
"nullable": []
},
"hash": "2040e7f0a9b66bc12dc89007b07bab9da5fdd1b7ee72d411a9989deb4ee506bb"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS(SELECT 1 FROM oauth_client_authorizations WHERE id=$1)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": [
null
]
},
"hash": "3f8bd0280a59ad4561ca652cebc7734a9af0e944f1671df71f9f4e25d835ffd9"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS(SELECT 1 FROM oauth_client_redirect_uris WHERE id=$1)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": [
null
]
},
"hash": "49d5360751072cc2cb5954cdecb31044f41d210dd64bbbb5e7c2347acc2304e9"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE oauth_access_tokens\n SET last_used = $2\n WHERE id IN\n (SELECT * FROM UNNEST($1::bigint[]))\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8Array",
"Timestamptz"
]
},
"nullable": []
},
"hash": "65c9f9cd010c14100839cd0b044103cac7e4b850d446b29d2efd9757b642fc1c"
}

View File

@@ -0,0 +1,47 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT id, client_id, user_id, scopes, created\n FROM oauth_client_authorizations\n WHERE client_id=$1 AND user_id=$2\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "client_id",
"type_info": "Int8"
},
{
"ordinal": 2,
"name": "user_id",
"type_info": "Int8"
},
{
"ordinal": 3,
"name": "scopes",
"type_info": "Int8"
},
{
"ordinal": 4,
"name": "created",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Int8",
"Int8"
]
},
"nullable": [
false,
false,
false,
false,
false
]
},
"hash": "65ddadc9d103ccb9d81e1f52565cff1889e5490f0d0d62170ed2b9515ffc5104"
}

View File

@@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO oauth_client_authorizations (\n id, client_id, user_id, scopes\n )\n VALUES (\n $1, $2, $3, $4\n )\n ON CONFLICT (id)\n DO UPDATE SET scopes = EXCLUDED.scopes\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Int8",
"Int8",
"Int8"
]
},
"nullable": []
},
"hash": "68ef15f50a067503dce124b50fb3c2efd07808c4a859ab1b1e9e65e16439a8f3"
}

View File

@@ -0,0 +1,32 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO oauth_access_tokens (\n id, authorization_id, token_hash, scopes, last_used\n )\n VALUES (\n $1, $2, $3, $4, $5\n )\n RETURNING created, expires\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "created",
"type_info": "Timestamptz"
},
{
"ordinal": 1,
"name": "expires",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Int8",
"Int8",
"Text",
"Int8",
"Timestamptz"
]
},
"nullable": [
false,
false
]
},
"hash": "6b881555e610ddc6796cdcbfd2de26e68b10522d0f1df3f006d58f6b72be9911"
}

View File

@@ -0,0 +1,70 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n tokens.id,\n tokens.authorization_id,\n tokens.token_hash,\n tokens.scopes,\n tokens.created,\n tokens.expires,\n tokens.last_used,\n auths.client_id,\n auths.user_id\n FROM oauth_access_tokens tokens\n JOIN oauth_client_authorizations auths\n ON tokens.authorization_id = auths.id\n WHERE tokens.token_hash = $1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "authorization_id",
"type_info": "Int8"
},
{
"ordinal": 2,
"name": "token_hash",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "scopes",
"type_info": "Int8"
},
{
"ordinal": 4,
"name": "created",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
"name": "expires",
"type_info": "Timestamptz"
},
{
"ordinal": 6,
"name": "last_used",
"type_info": "Timestamptz"
},
{
"ordinal": 7,
"name": "client_id",
"type_info": "Int8"
},
{
"ordinal": 8,
"name": "user_id",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false,
false,
false,
false,
false,
true,
false,
false
]
},
"hash": "7174cd941ff95260ad9c564daf92876c5ae253df538f4cd4c3701e63137fb01b"
}

View File

@@ -0,0 +1,70 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n clients.id as \"id!\",\n clients.name as \"name!\",\n clients.icon_url as \"icon_url?\",\n clients.max_scopes as \"max_scopes!\",\n clients.secret_hash as \"secret_hash!\",\n clients.created as \"created!\",\n clients.created_by as \"created_by!\",\n uris.uri_ids as \"uri_ids?\",\n uris.uri_vals as \"uri_vals?\"\n FROM oauth_clients clients\n LEFT JOIN (\n SELECT client_id, array_agg(id) as uri_ids, array_agg(uri) as uri_vals\n FROM oauth_client_redirect_uris\n GROUP BY client_id\n ) uris ON clients.id = uris.client_id\n WHERE created_by = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id!",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "name!",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "icon_url?",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "max_scopes!",
"type_info": "Int8"
},
{
"ordinal": 4,
"name": "secret_hash!",
"type_info": "Text"
},
{
"ordinal": 5,
"name": "created!",
"type_info": "Timestamptz"
},
{
"ordinal": 6,
"name": "created_by!",
"type_info": "Int8"
},
{
"ordinal": 7,
"name": "uri_ids?",
"type_info": "Int8Array"
},
{
"ordinal": 8,
"name": "uri_vals?",
"type_info": "TextArray"
}
],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": [
false,
false,
true,
false,
false,
false,
false,
null,
null
]
},
"hash": "8bfb350d4f539a110b05f42812ea2593a1556ef214f3bed519de6b6e21c7d477"
}

View File

@@ -0,0 +1,19 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO oauth_clients (\n id, name, icon_url, max_scopes, secret_hash, created_by\n )\n VALUES (\n $1, $2, $3, $4, $5, $6\n )\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Text",
"Text",
"Int8",
"Text",
"Int8"
]
},
"nullable": []
},
"hash": "93f6a94a9b288916dbf9999338d2278605311a311def3cbe38846b8ca465737f"
}

View File

@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO oauth_client_redirect_uris (id, client_id, uri)\n SELECT * FROM UNNEST($1::bigint[], $2::bigint[], $3::varchar[])\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8Array",
"Int8Array",
"VarcharArray"
]
},
"nullable": []
},
"hash": "9dadd6926a8429e60cb5fd53285b81f2f47ccdded1e764c04d8b7651d9796ce0"
}

View File

@@ -0,0 +1,46 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT id, client_id, user_id, scopes, created\n FROM oauth_client_authorizations\n WHERE user_id=$1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "client_id",
"type_info": "Int8"
},
{
"ordinal": 2,
"name": "user_id",
"type_info": "Int8"
},
{
"ordinal": 3,
"name": "scopes",
"type_info": "Int8"
},
{
"ordinal": 4,
"name": "created",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": [
false,
false,
false,
false,
false
]
},
"hash": "c5319631c46ffa46e218fcf308f17ef99fae60e5fbff5f0396f70787156de322"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "\n DELETE FROM oauth_client_redirect_uris\n WHERE id IN\n (SELECT * FROM UNNEST($1::bigint[]))\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8Array"
]
},
"nullable": []
},
"hash": "cf84f5e2a594a90b2e7993758807aaaaf533a4409633cf00c071049bb6816c96"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS(SELECT 1 FROM oauth_clients WHERE id=$1)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": [
null
]
},
"hash": "d0b2ddba90ce69a50d0260a191bf501784de06acdddeed1db8f570cb04755f1a"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n DELETE FROM oauth_client_authorizations\n WHERE client_id=$1 AND user_id=$2\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Int8"
]
},
"nullable": []
},
"hash": "db1deb79fa509974f1cd68cacd541c55bf62928a96d9582d3e223d6473335428"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS(SELECT 1 FROM oauth_access_tokens WHERE id=$1)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": [
null
]
},
"hash": "e15ff50bd75a49d50975d337f61f3412349dd6bc5c836d2634bbcb376a6f7c12"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "\n DELETE FROM oauth_clients\n WHERE id = $1\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": []
},
"hash": "e60f725571e7b7b716d19735ab3b8f3133bea215a89964d78cb652f930465faf"
}

View File

@@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE oauth_clients\n SET name = $1, icon_url = $2, max_scopes = $3\n WHERE (id = $4)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Int8",
"Int8"
]
},
"nullable": []
},
"hash": "e8cc8895ebc8b1904a43e00f1e123f75ffdaebc76d67a5d35218fa9273d46d53"
}

View File

@@ -0,0 +1,70 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n clients.id as \"id!\",\n clients.name as \"name!\",\n clients.icon_url as \"icon_url?\",\n clients.max_scopes as \"max_scopes!\",\n clients.secret_hash as \"secret_hash!\",\n clients.created as \"created!\",\n clients.created_by as \"created_by!\",\n uris.uri_ids as \"uri_ids?\",\n uris.uri_vals as \"uri_vals?\"\n FROM oauth_clients clients\n LEFT JOIN (\n SELECT client_id, array_agg(id) as uri_ids, array_agg(uri) as uri_vals\n FROM oauth_client_redirect_uris\n GROUP BY client_id\n ) uris ON clients.id = uris.client_id\n WHERE clients.id = ANY($1::bigint[])",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id!",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "name!",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "icon_url?",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "max_scopes!",
"type_info": "Int8"
},
{
"ordinal": 4,
"name": "secret_hash!",
"type_info": "Text"
},
{
"ordinal": 5,
"name": "created!",
"type_info": "Timestamptz"
},
{
"ordinal": 6,
"name": "created_by!",
"type_info": "Int8"
},
{
"ordinal": 7,
"name": "uri_ids?",
"type_info": "Int8Array"
},
{
"ordinal": 8,
"name": "uri_vals?",
"type_info": "TextArray"
}
],
"parameters": {
"Left": [
"Int8Array"
]
},
"nullable": [
true,
true,
true,
true,
true,
true,
true,
null,
null
]
},
"hash": "fdfb2433a8e407d42cec1791d67549ab5c23306758168af38f955c06d251b0b7"
}