You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-31 09:24:31 +03:00
Fix post-auth redirects & support max_age
This also displays some context on login and reauth page about the next step
This commit is contained in:
@ -54,8 +54,296 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"0cc63e00143cf94f63695be24acdcdffd8e8a3da50ea1ddf973a39bc34f861d4": {
|
"18d98b65c82142c28fb350f596c4439dbb04a55ff5b84586c1cb54601000d00d": {
|
||||||
"query": "\n SELECT\n og.id AS grant_id,\n og.created_at AS grant_created_at,\n og.cancelled_at AS grant_cancelled_at,\n og.fulfilled_at AS grant_fulfilled_at,\n og.exchanged_at AS grant_exchanged_at,\n og.scope AS grant_scope,\n og.state AS grant_state,\n og.redirect_uri AS grant_redirect_uri,\n og.response_mode AS grant_response_mode,\n og.nonce AS grant_nonce,\n og.max_age AS grant_max_age,\n og.acr_values AS grant_acr_values,\n og.client_id AS client_id,\n og.code AS grant_code,\n og.response_type_code AS grant_response_type_code,\n og.response_type_token AS grant_response_type_token,\n og.response_type_id_token AS grant_response_type_id_token,\n og.code_challenge AS grant_code_challenge,\n og.code_challenge_method AS grant_code_challenge_method,\n os.id AS \"session_id?\",\n us.id AS \"user_session_id?\",\n us.created_at AS \"user_session_created_at?\",\n u.id AS \"user_id?\",\n u.username AS \"user_username?\",\n usa.id AS \"user_session_last_authentication_id?\",\n usa.created_at AS \"user_session_last_authentication_created_at?\"\n FROM\n oauth2_authorization_grants og\n LEFT JOIN oauth2_sessions os\n ON os.id = og.oauth2_session_id\n LEFT JOIN user_sessions us\n ON us.id = os.user_session_id\n LEFT JOIN users u\n ON u.id = us.user_id\n LEFT JOIN user_session_authentications usa\n ON usa.session_id = us.id\n WHERE\n og.id = $1\n ",
|
"query": "\n SELECT\n og.id AS grant_id,\n og.created_at AS grant_created_at,\n og.cancelled_at AS grant_cancelled_at,\n og.fulfilled_at AS grant_fulfilled_at,\n og.exchanged_at AS grant_exchanged_at,\n og.scope AS grant_scope,\n og.state AS grant_state,\n og.redirect_uri AS grant_redirect_uri,\n og.response_mode AS grant_response_mode,\n og.nonce AS grant_nonce,\n og.max_age AS grant_max_age,\n og.acr_values AS grant_acr_values,\n og.client_id AS client_id,\n og.code AS grant_code,\n og.response_type_code AS grant_response_type_code,\n og.response_type_token AS grant_response_type_token,\n og.response_type_id_token AS grant_response_type_id_token,\n og.code_challenge AS grant_code_challenge,\n og.code_challenge_method AS grant_code_challenge_method,\n os.id AS \"session_id?\",\n us.id AS \"user_session_id?\",\n us.created_at AS \"user_session_created_at?\",\n u.id AS \"user_id?\",\n u.username AS \"user_username?\",\n usa.id AS \"user_session_last_authentication_id?\",\n usa.created_at AS \"user_session_last_authentication_created_at?\"\n FROM\n oauth2_authorization_grants og\n LEFT JOIN oauth2_sessions os\n ON os.id = og.oauth2_session_id\n LEFT JOIN user_sessions us\n ON us.id = os.user_session_id\n LEFT JOIN users u\n ON u.id = us.user_id\n LEFT JOIN user_session_authentications usa\n ON usa.session_id = us.id\n WHERE\n og.code = $1\n\n ORDER BY usa.created_at DESC\n LIMIT 1\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "grant_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "grant_created_at",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 2,
|
||||||
|
"name": "grant_cancelled_at",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 3,
|
||||||
|
"name": "grant_fulfilled_at",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 4,
|
||||||
|
"name": "grant_exchanged_at",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 5,
|
||||||
|
"name": "grant_scope",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 6,
|
||||||
|
"name": "grant_state",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 7,
|
||||||
|
"name": "grant_redirect_uri",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 8,
|
||||||
|
"name": "grant_response_mode",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 9,
|
||||||
|
"name": "grant_nonce",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 10,
|
||||||
|
"name": "grant_max_age",
|
||||||
|
"type_info": "Int4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 11,
|
||||||
|
"name": "grant_acr_values",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 12,
|
||||||
|
"name": "client_id",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 13,
|
||||||
|
"name": "grant_code",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 14,
|
||||||
|
"name": "grant_response_type_code",
|
||||||
|
"type_info": "Bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 15,
|
||||||
|
"name": "grant_response_type_token",
|
||||||
|
"type_info": "Bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 16,
|
||||||
|
"name": "grant_response_type_id_token",
|
||||||
|
"type_info": "Bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 17,
|
||||||
|
"name": "grant_code_challenge",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 18,
|
||||||
|
"name": "grant_code_challenge_method",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 19,
|
||||||
|
"name": "session_id?",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 20,
|
||||||
|
"name": "user_session_id?",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 21,
|
||||||
|
"name": "user_session_created_at?",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 22,
|
||||||
|
"name": "user_id?",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 23,
|
||||||
|
"name": "user_username?",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 24,
|
||||||
|
"name": "user_session_last_authentication_id?",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 25,
|
||||||
|
"name": "user_session_last_authentication_created_at?",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"2dbccaf2fb557dd36598bf4d00941280535cc523ac3a481903ed825088901bce": {
|
||||||
|
"query": "\n SELECT\n at.id AS \"access_token_id\",\n at.token AS \"access_token\",\n at.expires_after AS \"access_token_expires_after\",\n at.created_at AS \"access_token_created_at\",\n os.id AS \"session_id!\",\n os.client_id AS \"client_id!\",\n os.scope AS \"scope!\",\n us.id AS \"user_session_id!\",\n us.created_at AS \"user_session_created_at!\",\n u.id AS \"user_id!\",\n u.username AS \"user_username!\",\n usa.id AS \"user_session_last_authentication_id?\",\n usa.created_at AS \"user_session_last_authentication_created_at?\"\n\n FROM oauth2_access_tokens at\n INNER JOIN oauth2_sessions os\n ON os.id = at.oauth2_session_id\n INNER JOIN user_sessions us\n ON us.id = os.user_session_id\n INNER JOIN users u\n ON u.id = us.user_id\n LEFT JOIN user_session_authentications usa\n ON usa.session_id = us.id\n\n WHERE at.token = $1\n AND at.created_at + (at.expires_after * INTERVAL '1 second') >= now()\n AND us.active\n\n ORDER BY usa.created_at DESC\n LIMIT 1\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "access_token_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "access_token",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 2,
|
||||||
|
"name": "access_token_expires_after",
|
||||||
|
"type_info": "Int4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 3,
|
||||||
|
"name": "access_token_created_at",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 4,
|
||||||
|
"name": "session_id!",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 5,
|
||||||
|
"name": "client_id!",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 6,
|
||||||
|
"name": "scope!",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 7,
|
||||||
|
"name": "user_session_id!",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 8,
|
||||||
|
"name": "user_session_created_at!",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 9,
|
||||||
|
"name": "user_id!",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 10,
|
||||||
|
"name": "user_username!",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 11,
|
||||||
|
"name": "user_session_last_authentication_id?",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 12,
|
||||||
|
"name": "user_session_last_authentication_created_at?",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"307fd9f71e7a94a0a0d9ce523ee9792e127485d0d12480c43f179dd9b75afbab": {
|
||||||
|
"query": "\n INSERT INTO user_sessions (user_id)\n VALUES ($1)\n RETURNING id, created_at\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "created_at",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"3205a180aaa4661a016dada3a015ffd7a1019cd121e284f11e8120a6664e6288": {
|
||||||
|
"query": "\n SELECT\n og.id AS grant_id,\n og.created_at AS grant_created_at,\n og.cancelled_at AS grant_cancelled_at,\n og.fulfilled_at AS grant_fulfilled_at,\n og.exchanged_at AS grant_exchanged_at,\n og.scope AS grant_scope,\n og.state AS grant_state,\n og.redirect_uri AS grant_redirect_uri,\n og.response_mode AS grant_response_mode,\n og.nonce AS grant_nonce,\n og.max_age AS grant_max_age,\n og.acr_values AS grant_acr_values,\n og.client_id AS client_id,\n og.code AS grant_code,\n og.response_type_code AS grant_response_type_code,\n og.response_type_token AS grant_response_type_token,\n og.response_type_id_token AS grant_response_type_id_token,\n og.code_challenge AS grant_code_challenge,\n og.code_challenge_method AS grant_code_challenge_method,\n os.id AS \"session_id?\",\n us.id AS \"user_session_id?\",\n us.created_at AS \"user_session_created_at?\",\n u.id AS \"user_id?\",\n u.username AS \"user_username?\",\n usa.id AS \"user_session_last_authentication_id?\",\n usa.created_at AS \"user_session_last_authentication_created_at?\"\n FROM\n oauth2_authorization_grants og\n LEFT JOIN oauth2_sessions os\n ON os.id = og.oauth2_session_id\n LEFT JOIN user_sessions us\n ON us.id = os.user_session_id\n LEFT JOIN users u\n ON u.id = us.user_id\n LEFT JOIN user_session_authentications usa\n ON usa.session_id = us.id\n WHERE\n og.id = $1\n\n ORDER BY usa.created_at DESC\n LIMIT 1\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@ -224,124 +512,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"2dbccaf2fb557dd36598bf4d00941280535cc523ac3a481903ed825088901bce": {
|
|
||||||
"query": "\n SELECT\n at.id AS \"access_token_id\",\n at.token AS \"access_token\",\n at.expires_after AS \"access_token_expires_after\",\n at.created_at AS \"access_token_created_at\",\n os.id AS \"session_id!\",\n os.client_id AS \"client_id!\",\n os.scope AS \"scope!\",\n us.id AS \"user_session_id!\",\n us.created_at AS \"user_session_created_at!\",\n u.id AS \"user_id!\",\n u.username AS \"user_username!\",\n usa.id AS \"user_session_last_authentication_id?\",\n usa.created_at AS \"user_session_last_authentication_created_at?\"\n\n FROM oauth2_access_tokens at\n INNER JOIN oauth2_sessions os\n ON os.id = at.oauth2_session_id\n INNER JOIN user_sessions us\n ON us.id = os.user_session_id\n INNER JOIN users u\n ON u.id = us.user_id\n LEFT JOIN user_session_authentications usa\n ON usa.session_id = us.id\n\n WHERE at.token = $1\n AND at.created_at + (at.expires_after * INTERVAL '1 second') >= now()\n AND us.active\n\n ORDER BY usa.created_at DESC\n LIMIT 1\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"ordinal": 0,
|
|
||||||
"name": "access_token_id",
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 1,
|
|
||||||
"name": "access_token",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 2,
|
|
||||||
"name": "access_token_expires_after",
|
|
||||||
"type_info": "Int4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 3,
|
|
||||||
"name": "access_token_created_at",
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 4,
|
|
||||||
"name": "session_id!",
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 5,
|
|
||||||
"name": "client_id!",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 6,
|
|
||||||
"name": "scope!",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 7,
|
|
||||||
"name": "user_session_id!",
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 8,
|
|
||||||
"name": "user_session_created_at!",
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 9,
|
|
||||||
"name": "user_id!",
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 10,
|
|
||||||
"name": "user_username!",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 11,
|
|
||||||
"name": "user_session_last_authentication_id?",
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 12,
|
|
||||||
"name": "user_session_last_authentication_created_at?",
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Text"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"307fd9f71e7a94a0a0d9ce523ee9792e127485d0d12480c43f179dd9b75afbab": {
|
|
||||||
"query": "\n INSERT INTO user_sessions (user_id)\n VALUES ($1)\n RETURNING id, created_at\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"ordinal": 0,
|
|
||||||
"name": "id",
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 1,
|
|
||||||
"name": "created_at",
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"38641231a3bff71252e8bc0ead3a033c9148762ea64d707642551c01a4c89b84": {
|
"38641231a3bff71252e8bc0ead3a033c9148762ea64d707642551c01a4c89b84": {
|
||||||
"query": "\n INSERT INTO oauth2_authorization_grants\n (client_id, redirect_uri, scope, state, nonce, max_age,\n acr_values, response_mode, code_challenge, code_challenge_method,\n response_type_code, response_type_token, response_type_id_token,\n code)\n VALUES\n ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)\n RETURNING id, created_at\n ",
|
"query": "\n INSERT INTO oauth2_authorization_grants\n (client_id, redirect_uri, scope, state, nonce, max_age,\n acr_values, response_mode, code_challenge, code_challenge_method,\n response_type_code, response_type_token, response_type_id_token,\n code)\n VALUES\n ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)\n RETURNING id, created_at\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
@ -612,176 +782,6 @@
|
|||||||
"nullable": []
|
"nullable": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"8dde452a37c8faad20df68eb2b665202e0fb6b4ce805138e5f19d4e7eb0ce802": {
|
|
||||||
"query": "\n SELECT\n og.id AS grant_id,\n og.created_at AS grant_created_at,\n og.cancelled_at AS grant_cancelled_at,\n og.fulfilled_at AS grant_fulfilled_at,\n og.exchanged_at AS grant_exchanged_at,\n og.scope AS grant_scope,\n og.state AS grant_state,\n og.redirect_uri AS grant_redirect_uri,\n og.response_mode AS grant_response_mode,\n og.nonce AS grant_nonce,\n og.max_age AS grant_max_age,\n og.acr_values AS grant_acr_values,\n og.client_id AS client_id,\n og.code AS grant_code,\n og.response_type_code AS grant_response_type_code,\n og.response_type_token AS grant_response_type_token,\n og.response_type_id_token AS grant_response_type_id_token,\n og.code_challenge AS grant_code_challenge,\n og.code_challenge_method AS grant_code_challenge_method,\n os.id AS \"session_id?\",\n us.id AS \"user_session_id?\",\n us.created_at AS \"user_session_created_at?\",\n u.id AS \"user_id?\",\n u.username AS \"user_username?\",\n usa.id AS \"user_session_last_authentication_id?\",\n usa.created_at AS \"user_session_last_authentication_created_at?\"\n FROM\n oauth2_authorization_grants og\n LEFT JOIN oauth2_sessions os\n ON os.id = og.oauth2_session_id\n LEFT JOIN user_sessions us\n ON us.id = os.user_session_id\n LEFT JOIN users u\n ON u.id = us.user_id\n LEFT JOIN user_session_authentications usa\n ON usa.session_id = us.id\n WHERE\n og.code = $1\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"ordinal": 0,
|
|
||||||
"name": "grant_id",
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 1,
|
|
||||||
"name": "grant_created_at",
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 2,
|
|
||||||
"name": "grant_cancelled_at",
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 3,
|
|
||||||
"name": "grant_fulfilled_at",
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 4,
|
|
||||||
"name": "grant_exchanged_at",
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 5,
|
|
||||||
"name": "grant_scope",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 6,
|
|
||||||
"name": "grant_state",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 7,
|
|
||||||
"name": "grant_redirect_uri",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 8,
|
|
||||||
"name": "grant_response_mode",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 9,
|
|
||||||
"name": "grant_nonce",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 10,
|
|
||||||
"name": "grant_max_age",
|
|
||||||
"type_info": "Int4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 11,
|
|
||||||
"name": "grant_acr_values",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 12,
|
|
||||||
"name": "client_id",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 13,
|
|
||||||
"name": "grant_code",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 14,
|
|
||||||
"name": "grant_response_type_code",
|
|
||||||
"type_info": "Bool"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 15,
|
|
||||||
"name": "grant_response_type_token",
|
|
||||||
"type_info": "Bool"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 16,
|
|
||||||
"name": "grant_response_type_id_token",
|
|
||||||
"type_info": "Bool"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 17,
|
|
||||||
"name": "grant_code_challenge",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 18,
|
|
||||||
"name": "grant_code_challenge_method",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 19,
|
|
||||||
"name": "session_id?",
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 20,
|
|
||||||
"name": "user_session_id?",
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 21,
|
|
||||||
"name": "user_session_created_at?",
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 22,
|
|
||||||
"name": "user_id?",
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 23,
|
|
||||||
"name": "user_username?",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 24,
|
|
||||||
"name": "user_session_last_authentication_id?",
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 25,
|
|
||||||
"name": "user_session_last_authentication_created_at?",
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Text"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"a09dfe1019110f2ec6eba0d35bafa467ab4b7980dd8b556826f03863f8edb0ab": {
|
"a09dfe1019110f2ec6eba0d35bafa467ab4b7980dd8b556826f03863f8edb0ab": {
|
||||||
"query": "UPDATE user_sessions SET active = FALSE WHERE id = $1",
|
"query": "UPDATE user_sessions SET active = FALSE WHERE id = $1",
|
||||||
"describe": {
|
"describe": {
|
||||||
|
@ -186,7 +186,7 @@ impl EncryptedCookieSaver {
|
|||||||
// TODO: make those options customizable
|
// TODO: make those options customizable
|
||||||
let value = Cookie::build(T::cookie_key(), encrypted)
|
let value = Cookie::build(T::cookie_key(), encrypted)
|
||||||
.http_only(true)
|
.http_only(true)
|
||||||
.same_site(SameSite::Strict)
|
.same_site(SameSite::Lax)
|
||||||
.finish()
|
.finish()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
|
@ -14,8 +14,7 @@
|
|||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
convert::{TryFrom, TryInto},
|
convert::TryFrom,
|
||||||
num::NonZeroU32,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
@ -40,7 +39,7 @@ use oauth2_types::{
|
|||||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use sqlx::{PgPool, Postgres, Transaction};
|
use sqlx::{PgExecutor, PgPool, Postgres, Transaction};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use warp::{
|
use warp::{
|
||||||
redirect::see_other,
|
redirect::see_other,
|
||||||
@ -354,14 +353,6 @@ async fn get(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let max_age: Option<NonZeroU32> = params
|
|
||||||
.auth
|
|
||||||
.max_age
|
|
||||||
.as_ref()
|
|
||||||
.map(|d| d.num_seconds().try_into().and_then(|d: u32| d.try_into()))
|
|
||||||
.transpose()
|
|
||||||
.wrap_error()?;
|
|
||||||
|
|
||||||
let grant = new_authorization_grant(
|
let grant = new_authorization_grant(
|
||||||
&mut txn,
|
&mut txn,
|
||||||
client.client_id.clone(),
|
client.client_id.clone(),
|
||||||
@ -370,7 +361,7 @@ async fn get(
|
|||||||
code,
|
code,
|
||||||
params.auth.state,
|
params.auth.state,
|
||||||
params.auth.nonce,
|
params.auth.nonce,
|
||||||
max_age,
|
params.auth.max_age,
|
||||||
None,
|
None,
|
||||||
response_mode,
|
response_mode,
|
||||||
response_type.contains(&ResponseType::Token),
|
response_type.contains(&ResponseType::Token),
|
||||||
@ -397,6 +388,14 @@ async fn get(
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub(crate) struct ContinueAuthorizationGrant<S: StorageBackend> {
|
pub(crate) struct ContinueAuthorizationGrant<S: StorageBackend> {
|
||||||
|
#[serde(
|
||||||
|
with = "serde_with::rust::display_fromstr",
|
||||||
|
bound(
|
||||||
|
deserialize = "S::AuthorizationGrantData: std::str::FromStr,
|
||||||
|
<S::AuthorizationGrantData as std::str::FromStr>::Err: std::fmt::Display",
|
||||||
|
serialize = "S::AuthorizationGrantData: std::fmt::Display"
|
||||||
|
)
|
||||||
|
)]
|
||||||
data: S::AuthorizationGrantData,
|
data: S::AuthorizationGrantData,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,7 +406,7 @@ impl<S: StorageBackend> ContinueAuthorizationGrant<S> {
|
|||||||
|
|
||||||
pub fn build_uri(&self) -> anyhow::Result<Uri>
|
pub fn build_uri(&self) -> anyhow::Result<Uri>
|
||||||
where
|
where
|
||||||
S::AuthorizationGrantData: Serialize,
|
S::AuthorizationGrantData: std::fmt::Display,
|
||||||
{
|
{
|
||||||
let qs = serde_urlencoded::to_string(self)?;
|
let qs = serde_urlencoded::to_string(self)?;
|
||||||
let path_and_query = PathAndQuery::try_from(format!("/oauth2/authorize/step?{}", qs))?;
|
let path_and_query = PathAndQuery::try_from(format!("/oauth2/authorize/step?{}", qs))?;
|
||||||
@ -420,6 +419,15 @@ impl<S: StorageBackend> ContinueAuthorizationGrant<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ContinueAuthorizationGrant<PostgresqlBackend> {
|
||||||
|
pub async fn fetch_authorization_grant(
|
||||||
|
&self,
|
||||||
|
executor: impl PgExecutor<'_>,
|
||||||
|
) -> anyhow::Result<AuthorizationGrant<PostgresqlBackend>> {
|
||||||
|
get_grant_by_id(executor, self.data).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn step(
|
async fn step(
|
||||||
next: ContinueAuthorizationGrant<PostgresqlBackend>,
|
next: ContinueAuthorizationGrant<PostgresqlBackend>,
|
||||||
browser_session: BrowserSession<PostgresqlBackend>,
|
browser_session: BrowserSession<PostgresqlBackend>,
|
||||||
@ -427,14 +435,17 @@ async fn step(
|
|||||||
) -> Result<ReplyOrBackToClient, Rejection> {
|
) -> Result<ReplyOrBackToClient, Rejection> {
|
||||||
// TODO: we should check if the grant here was started by the browser doing that
|
// TODO: we should check if the grant here was started by the browser doing that
|
||||||
// request using a signed cookie
|
// request using a signed cookie
|
||||||
let grant = get_grant_by_id(&mut txn, next.data).await.wrap_error()?;
|
let grant = next
|
||||||
|
.fetch_authorization_grant(&mut txn)
|
||||||
|
.await
|
||||||
|
.wrap_error()?;
|
||||||
|
|
||||||
if !matches!(grant.stage, AuthorizationGrantStage::Pending) {
|
if !matches!(grant.stage, AuthorizationGrantStage::Pending) {
|
||||||
return Err(anyhow::anyhow!("authorization grant not pending")).wrap_error();
|
return Err(anyhow::anyhow!("authorization grant not pending")).wrap_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
let reply = match browser_session.last_authentication {
|
let reply = match browser_session.last_authentication {
|
||||||
Some(Authentication { created_at, .. }) if created_at < grant.max_auth_time() => {
|
Some(Authentication { created_at, .. }) if created_at > grant.max_auth_time() => {
|
||||||
let session = derive_session(&mut txn, &grant, browser_session)
|
let session = derive_session(&mut txn, &grant, browser_session)
|
||||||
.await
|
.await
|
||||||
.wrap_error()?;
|
.wrap_error()?;
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use chrono::Duration;
|
use chrono::{DateTime, Duration, Utc};
|
||||||
use data_encoding::BASE64URL_NOPAD;
|
use data_encoding::BASE64URL_NOPAD;
|
||||||
use headers::{CacheControl, Pragma};
|
use headers::{CacheControl, Pragma};
|
||||||
use hyper::{Method, StatusCode};
|
use hyper::{Method, StatusCode};
|
||||||
@ -29,7 +29,7 @@ use oauth2_types::{
|
|||||||
};
|
};
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::{serde_as, skip_serializing_none};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use sqlx::{pool::PoolConnection, Acquire, PgPool, Postgres};
|
use sqlx::{pool::PoolConnection, Acquire, PgPool, Postgres};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
@ -58,6 +58,7 @@ use crate::{
|
|||||||
tokens::{AccessToken, RefreshToken},
|
tokens::{AccessToken, RefreshToken},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[serde_as]
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug)]
|
||||||
struct CustomClaims {
|
struct CustomClaims {
|
||||||
@ -68,6 +69,8 @@ struct CustomClaims {
|
|||||||
#[serde(rename = "aud")]
|
#[serde(rename = "aud")]
|
||||||
audiences: Vec<String>,
|
audiences: Vec<String>,
|
||||||
nonce: Option<String>,
|
nonce: Option<String>,
|
||||||
|
#[serde_as(as = "Option<serde_with::TimestampSeconds>")]
|
||||||
|
auth_time: Option<DateTime<Utc>>,
|
||||||
at_hash: String,
|
at_hash: String,
|
||||||
c_hash: String,
|
c_hash: String,
|
||||||
}
|
}
|
||||||
@ -253,6 +256,10 @@ async fn authorization_code_grant(
|
|||||||
subject: browser_session.user.sub.clone(),
|
subject: browser_session.user.sub.clone(),
|
||||||
audiences: vec![client.client_id.clone()],
|
audiences: vec![client.client_id.clone()],
|
||||||
nonce: authz_grant.nonce.clone(),
|
nonce: authz_grant.nonce.clone(),
|
||||||
|
auth_time: browser_session
|
||||||
|
.last_authentication
|
||||||
|
.as_ref()
|
||||||
|
.map(|a| a.created_at),
|
||||||
at_hash: hash(Sha256::new(), &access_token_str).wrap_error()?,
|
at_hash: hash(Sha256::new(), &access_token_str).wrap_error()?,
|
||||||
c_hash: hash(Sha256::new(), &grant.code).wrap_error()?,
|
c_hash: hash(Sha256::new(), &grant.code).wrap_error()?,
|
||||||
})
|
})
|
||||||
|
@ -17,7 +17,7 @@ use std::convert::TryFrom;
|
|||||||
use hyper::http::uri::{Parts, PathAndQuery, Uri};
|
use hyper::http::uri::{Parts, PathAndQuery, Uri};
|
||||||
use mas_data_model::{errors::WrapFormError, BrowserSession, StorageBackend};
|
use mas_data_model::{errors::WrapFormError, BrowserSession, StorageBackend};
|
||||||
use mas_templates::{LoginContext, LoginFormField, TemplateContext, Templates};
|
use mas_templates::{LoginContext, LoginFormField, TemplateContext, Templates};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Deserialize;
|
||||||
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
||||||
use warp::{reply::html, Filter, Rejection, Reply};
|
use warp::{reply::html, Filter, Rejection, Reply};
|
||||||
|
|
||||||
@ -36,27 +36,27 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(
|
#[serde(bound(deserialize = "S::AuthorizationGrantData: std::str::FromStr,
|
||||||
rename_all = "snake_case",
|
<S::AuthorizationGrantData as std::str::FromStr>::Err: std::fmt::Display"))]
|
||||||
bound = "<S as StorageBackend>::AuthorizationGrantData: Deserialize<'de>"
|
|
||||||
)]
|
|
||||||
pub(crate) struct LoginRequest<S: StorageBackend> {
|
pub(crate) struct LoginRequest<S: StorageBackend> {
|
||||||
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
#[serde(flatten)]
|
||||||
next: Option<PostAuthAction<S>>,
|
post_auth_action: Option<PostAuthAction<S>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: StorageBackend> From<PostAuthAction<S>> for LoginRequest<S> {
|
impl<S: StorageBackend> From<PostAuthAction<S>> for LoginRequest<S> {
|
||||||
fn from(next: PostAuthAction<S>) -> Self {
|
fn from(post_auth_action: PostAuthAction<S>) -> Self {
|
||||||
Self { next: Some(next) }
|
Self {
|
||||||
|
post_auth_action: Some(post_auth_action),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: StorageBackend> LoginRequest<S> {
|
impl<S: StorageBackend> LoginRequest<S> {
|
||||||
pub fn build_uri(&self) -> anyhow::Result<Uri>
|
pub fn build_uri(&self) -> anyhow::Result<Uri>
|
||||||
where
|
where
|
||||||
S::AuthorizationGrantData: Serialize,
|
S::AuthorizationGrantData: std::fmt::Display,
|
||||||
{
|
{
|
||||||
let path_and_query = if let Some(next) = &self.next {
|
let path_and_query = if let Some(next) = &self.post_auth_action {
|
||||||
let qs = serde_urlencoded::to_string(next)?;
|
let qs = serde_urlencoded::to_string(next)?;
|
||||||
PathAndQuery::try_from(format!("/login?{}", qs))?
|
PathAndQuery::try_from(format!("/login?{}", qs))?
|
||||||
} else {
|
} else {
|
||||||
@ -72,10 +72,10 @@ impl<S: StorageBackend> LoginRequest<S> {
|
|||||||
|
|
||||||
fn redirect(self) -> Result<impl Reply, Rejection>
|
fn redirect(self) -> Result<impl Reply, Rejection>
|
||||||
where
|
where
|
||||||
S::AuthorizationGrantData: Serialize,
|
S::AuthorizationGrantData: std::fmt::Display,
|
||||||
{
|
{
|
||||||
let uri = self
|
let uri = self
|
||||||
.next
|
.post_auth_action
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(PostAuthAction::build_uri)
|
.map(PostAuthAction::build_uri)
|
||||||
.transpose()
|
.transpose()
|
||||||
@ -99,6 +99,7 @@ pub(super) fn filter(
|
|||||||
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone + Send + Sync + 'static {
|
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone + Send + Sync + 'static {
|
||||||
let get = warp::get()
|
let get = warp::get()
|
||||||
.and(with_templates(templates))
|
.and(with_templates(templates))
|
||||||
|
.and(connection(pool))
|
||||||
.and(encrypted_cookie_saver(cookies_config))
|
.and(encrypted_cookie_saver(cookies_config))
|
||||||
.and(updated_csrf_token(cookies_config, csrf_config))
|
.and(updated_csrf_token(cookies_config, csrf_config))
|
||||||
.and(warp::query())
|
.and(warp::query())
|
||||||
@ -119,6 +120,7 @@ pub(super) fn filter(
|
|||||||
|
|
||||||
async fn get(
|
async fn get(
|
||||||
templates: Templates,
|
templates: Templates,
|
||||||
|
mut conn: PoolConnection<Postgres>,
|
||||||
cookie_saver: EncryptedCookieSaver,
|
cookie_saver: EncryptedCookieSaver,
|
||||||
csrf_token: CsrfToken,
|
csrf_token: CsrfToken,
|
||||||
query: LoginRequest<PostgresqlBackend>,
|
query: LoginRequest<PostgresqlBackend>,
|
||||||
@ -127,7 +129,15 @@ async fn get(
|
|||||||
if maybe_session.is_some() {
|
if maybe_session.is_some() {
|
||||||
Ok(Box::new(query.redirect()?))
|
Ok(Box::new(query.redirect()?))
|
||||||
} else {
|
} else {
|
||||||
let ctx = LoginContext::default().with_csrf(csrf_token.form_value());
|
let ctx = LoginContext::default();
|
||||||
|
let ctx = match query.post_auth_action {
|
||||||
|
Some(next) => {
|
||||||
|
let next = next.load_context(&mut conn).await.wrap_error()?;
|
||||||
|
ctx.with_post_action(next)
|
||||||
|
}
|
||||||
|
None => ctx,
|
||||||
|
};
|
||||||
|
let ctx = ctx.with_csrf(csrf_token.form_value());
|
||||||
let content = templates.render_login(&ctx)?;
|
let content = templates.render_login(&ctx)?;
|
||||||
let reply = html(content);
|
let reply = html(content);
|
||||||
let reply = cookie_saver.save_encrypted(&csrf_token, reply)?;
|
let reply = cookie_saver.save_encrypted(&csrf_token, reply)?;
|
||||||
@ -158,8 +168,9 @@ async fn post(
|
|||||||
LoginError::Authentication { .. } => e.on_field(LoginFormField::Password),
|
LoginError::Authentication { .. } => e.on_field(LoginFormField::Password),
|
||||||
LoginError::Other(_) => e.on_form(),
|
LoginError::Other(_) => e.on_form(),
|
||||||
};
|
};
|
||||||
let ctx =
|
let ctx = LoginContext::default()
|
||||||
LoginContext::with_form_error(errored_form).with_csrf(csrf_token.form_value());
|
.with_form_error(errored_form)
|
||||||
|
.with_csrf(csrf_token.form_value());
|
||||||
let content = templates.render_login(&ctx)?;
|
let content = templates.render_login(&ctx)?;
|
||||||
let reply = html(content);
|
let reply = html(content);
|
||||||
let reply = cookie_saver.save_encrypted(&csrf_token, reply)?;
|
let reply = cookie_saver.save_encrypted(&csrf_token, reply)?;
|
||||||
@ -167,3 +178,15 @@ async fn post(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_login_request() {
|
||||||
|
let res: Result<LoginRequest<PostgresqlBackend>, _> =
|
||||||
|
serde_urlencoded::from_str("next=continue_authorization_grant&data=13");
|
||||||
|
res.unwrap().post_auth_action.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -16,9 +16,9 @@ use std::convert::TryFrom;
|
|||||||
|
|
||||||
use hyper::http::uri::{Parts, PathAndQuery};
|
use hyper::http::uri::{Parts, PathAndQuery};
|
||||||
use mas_data_model::{BrowserSession, StorageBackend};
|
use mas_data_model::{BrowserSession, StorageBackend};
|
||||||
use mas_templates::{EmptyContext, TemplateContext, Templates};
|
use mas_templates::{ReauthContext, TemplateContext, Templates};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Deserialize;
|
||||||
use sqlx::{PgPool, Postgres, Transaction};
|
use sqlx::{pool::PoolConnection, PgPool, Postgres, Transaction};
|
||||||
use warp::{hyper::Uri, reply::html, Filter, Rejection, Reply};
|
use warp::{hyper::Uri, reply::html, Filter, Rejection, Reply};
|
||||||
|
|
||||||
use super::PostAuthAction;
|
use super::PostAuthAction;
|
||||||
@ -28,34 +28,34 @@ use crate::{
|
|||||||
filters::{
|
filters::{
|
||||||
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
||||||
csrf::{protected_form, updated_csrf_token},
|
csrf::{protected_form, updated_csrf_token},
|
||||||
database::transaction,
|
database::{connection, transaction},
|
||||||
session::session,
|
session::session,
|
||||||
with_templates, CsrfToken,
|
with_templates, CsrfToken,
|
||||||
},
|
},
|
||||||
storage::{user::authenticate_session, PostgresqlBackend},
|
storage::{user::authenticate_session, PostgresqlBackend},
|
||||||
};
|
};
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(
|
#[serde(bound(deserialize = "S::AuthorizationGrantData: std::str::FromStr,
|
||||||
rename_all = "snake_case",
|
<S::AuthorizationGrantData as std::str::FromStr>::Err: std::fmt::Display"))]
|
||||||
bound = "<S as StorageBackend>::AuthorizationGrantData: Deserialize<'de>"
|
|
||||||
)]
|
|
||||||
pub(crate) struct ReauthRequest<S: StorageBackend> {
|
pub(crate) struct ReauthRequest<S: StorageBackend> {
|
||||||
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
#[serde(flatten)]
|
||||||
next: Option<PostAuthAction<S>>,
|
post_auth_action: Option<PostAuthAction<S>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: StorageBackend> From<PostAuthAction<S>> for ReauthRequest<S> {
|
impl<S: StorageBackend> From<PostAuthAction<S>> for ReauthRequest<S> {
|
||||||
fn from(next: PostAuthAction<S>) -> Self {
|
fn from(post_auth_action: PostAuthAction<S>) -> Self {
|
||||||
Self { next: Some(next) }
|
Self {
|
||||||
|
post_auth_action: Some(post_auth_action),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: StorageBackend> ReauthRequest<S> {
|
impl<S: StorageBackend> ReauthRequest<S> {
|
||||||
pub fn build_uri(&self) -> anyhow::Result<Uri>
|
pub fn build_uri(&self) -> anyhow::Result<Uri>
|
||||||
where
|
where
|
||||||
S::AuthorizationGrantData: Serialize,
|
S::AuthorizationGrantData: std::fmt::Display,
|
||||||
{
|
{
|
||||||
let path_and_query = if let Some(next) = &self.next {
|
let path_and_query = if let Some(next) = &self.post_auth_action {
|
||||||
let qs = serde_urlencoded::to_string(next)?;
|
let qs = serde_urlencoded::to_string(next)?;
|
||||||
PathAndQuery::try_from(format!("/reauth?{}", qs))?
|
PathAndQuery::try_from(format!("/reauth?{}", qs))?
|
||||||
} else {
|
} else {
|
||||||
@ -71,10 +71,10 @@ impl<S: StorageBackend> ReauthRequest<S> {
|
|||||||
|
|
||||||
fn redirect(self) -> Result<impl Reply, Rejection>
|
fn redirect(self) -> Result<impl Reply, Rejection>
|
||||||
where
|
where
|
||||||
S::AuthorizationGrantData: Serialize,
|
S::AuthorizationGrantData: std::fmt::Display,
|
||||||
{
|
{
|
||||||
let uri = self
|
let uri = self
|
||||||
.next
|
.post_auth_action
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(PostAuthAction::build_uri)
|
.map(PostAuthAction::build_uri)
|
||||||
.transpose()
|
.transpose()
|
||||||
@ -97,6 +97,7 @@ pub(super) fn filter(
|
|||||||
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone + Send + Sync + 'static {
|
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone + Send + Sync + 'static {
|
||||||
let get = warp::get()
|
let get = warp::get()
|
||||||
.and(with_templates(templates))
|
.and(with_templates(templates))
|
||||||
|
.and(connection(pool))
|
||||||
.and(encrypted_cookie_saver(cookies_config))
|
.and(encrypted_cookie_saver(cookies_config))
|
||||||
.and(updated_csrf_token(cookies_config, csrf_config))
|
.and(updated_csrf_token(cookies_config, csrf_config))
|
||||||
.and(session(pool, cookies_config))
|
.and(session(pool, cookies_config))
|
||||||
@ -115,14 +116,21 @@ pub(super) fn filter(
|
|||||||
|
|
||||||
async fn get(
|
async fn get(
|
||||||
templates: Templates,
|
templates: Templates,
|
||||||
|
mut conn: PoolConnection<Postgres>,
|
||||||
cookie_saver: EncryptedCookieSaver,
|
cookie_saver: EncryptedCookieSaver,
|
||||||
csrf_token: CsrfToken,
|
csrf_token: CsrfToken,
|
||||||
session: BrowserSession<PostgresqlBackend>,
|
session: BrowserSession<PostgresqlBackend>,
|
||||||
_query: ReauthRequest<PostgresqlBackend>,
|
query: ReauthRequest<PostgresqlBackend>,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let ctx = EmptyContext
|
let ctx = ReauthContext::default();
|
||||||
.with_session(session)
|
let ctx = match query.post_auth_action {
|
||||||
.with_csrf(csrf_token.form_value());
|
Some(next) => {
|
||||||
|
let next = next.load_context(&mut conn).await.wrap_error()?;
|
||||||
|
ctx.with_post_action(next)
|
||||||
|
}
|
||||||
|
None => ctx,
|
||||||
|
};
|
||||||
|
let ctx = ctx.with_session(session).with_csrf(csrf_token.form_value());
|
||||||
|
|
||||||
let content = templates.render_reauth(&ctx)?;
|
let content = templates.render_reauth(&ctx)?;
|
||||||
let reply = html(content);
|
let reply = html(content);
|
||||||
@ -136,6 +144,7 @@ async fn post(
|
|||||||
form: ReauthForm,
|
form: ReauthForm,
|
||||||
query: ReauthRequest<PostgresqlBackend>,
|
query: ReauthRequest<PostgresqlBackend>,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
|
// TODO: recover from errors here
|
||||||
authenticate_session(&mut txn, &session, form.password)
|
authenticate_session(&mut txn, &session, form.password)
|
||||||
.await
|
.await
|
||||||
.wrap_error()?;
|
.wrap_error()?;
|
||||||
|
@ -14,16 +14,20 @@
|
|||||||
|
|
||||||
use hyper::Uri;
|
use hyper::Uri;
|
||||||
use mas_data_model::StorageBackend;
|
use mas_data_model::StorageBackend;
|
||||||
|
use mas_templates::PostAuthContext;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::PgExecutor;
|
||||||
|
|
||||||
use super::super::oauth2::ContinueAuthorizationGrant;
|
use super::super::oauth2::ContinueAuthorizationGrant;
|
||||||
|
use crate::storage::PostgresqlBackend;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "snake_case", tag = "next")]
|
#[serde(rename_all = "snake_case", tag = "next")]
|
||||||
pub(crate) enum PostAuthAction<S: StorageBackend> {
|
pub(crate) enum PostAuthAction<S: StorageBackend> {
|
||||||
#[serde(bound(
|
#[serde(bound(
|
||||||
deserialize = "S::AuthorizationGrantData: Deserialize<'de>",
|
deserialize = "S::AuthorizationGrantData: std::str::FromStr,
|
||||||
serialize = "S::AuthorizationGrantData: Serialize"
|
<S::AuthorizationGrantData as std::str::FromStr>::Err: std::fmt::Display",
|
||||||
|
serialize = "S::AuthorizationGrantData: std::fmt::Display"
|
||||||
))]
|
))]
|
||||||
ContinueAuthorizationGrant(ContinueAuthorizationGrant<S>),
|
ContinueAuthorizationGrant(ContinueAuthorizationGrant<S>),
|
||||||
}
|
}
|
||||||
@ -31,7 +35,7 @@ pub(crate) enum PostAuthAction<S: StorageBackend> {
|
|||||||
impl<S: StorageBackend> PostAuthAction<S> {
|
impl<S: StorageBackend> PostAuthAction<S> {
|
||||||
pub fn build_uri(&self) -> anyhow::Result<Uri>
|
pub fn build_uri(&self) -> anyhow::Result<Uri>
|
||||||
where
|
where
|
||||||
S::AuthorizationGrantData: Serialize,
|
S::AuthorizationGrantData: std::fmt::Display,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
PostAuthAction::ContinueAuthorizationGrant(c) => c.build_uri(),
|
PostAuthAction::ContinueAuthorizationGrant(c) => c.build_uri(),
|
||||||
@ -44,3 +48,18 @@ impl<S: StorageBackend> From<ContinueAuthorizationGrant<S>> for PostAuthAction<S
|
|||||||
Self::ContinueAuthorizationGrant(g)
|
Self::ContinueAuthorizationGrant(g)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PostAuthAction<PostgresqlBackend> {
|
||||||
|
pub async fn load_context<'e>(
|
||||||
|
&self,
|
||||||
|
executor: impl PgExecutor<'e>,
|
||||||
|
) -> anyhow::Result<PostAuthContext> {
|
||||||
|
match self {
|
||||||
|
Self::ContinueAuthorizationGrant(c) => {
|
||||||
|
let grant = c.fetch_authorization_grant(executor).await?;
|
||||||
|
let grant = grant.into();
|
||||||
|
Ok(PostAuthContext::ContinueAuthorizationGrant { grant })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -121,7 +121,6 @@ struct GrantLookup {
|
|||||||
grant_redirect_uri: String,
|
grant_redirect_uri: String,
|
||||||
grant_response_mode: String,
|
grant_response_mode: String,
|
||||||
grant_nonce: Option<String>,
|
grant_nonce: Option<String>,
|
||||||
#[allow(dead_code)]
|
|
||||||
grant_max_age: Option<i32>,
|
grant_max_age: Option<i32>,
|
||||||
grant_acr_values: Option<String>,
|
grant_acr_values: Option<String>,
|
||||||
grant_response_type_code: bool,
|
grant_response_type_code: bool,
|
||||||
@ -274,6 +273,15 @@ impl TryInto<AuthorizationGrant<PostgresqlBackend>> for GrantLookup {
|
|||||||
.parse()
|
.parse()
|
||||||
.map_err(|_e| DatabaseInconsistencyError)?;
|
.map_err(|_e| DatabaseInconsistencyError)?;
|
||||||
|
|
||||||
|
let max_age = self
|
||||||
|
.grant_max_age
|
||||||
|
.map(|m: i32| m.try_into())
|
||||||
|
.transpose()
|
||||||
|
.map_err(|_e| DatabaseInconsistencyError)?
|
||||||
|
.map(|m: u32| m.try_into())
|
||||||
|
.transpose()
|
||||||
|
.map_err(|_e| DatabaseInconsistencyError)?;
|
||||||
|
|
||||||
Ok(AuthorizationGrant {
|
Ok(AuthorizationGrant {
|
||||||
data: self.grant_id,
|
data: self.grant_id,
|
||||||
stage,
|
stage,
|
||||||
@ -283,7 +291,7 @@ impl TryInto<AuthorizationGrant<PostgresqlBackend>> for GrantLookup {
|
|||||||
scope,
|
scope,
|
||||||
state: self.grant_state,
|
state: self.grant_state,
|
||||||
nonce: self.grant_nonce,
|
nonce: self.grant_nonce,
|
||||||
max_age: None, // TODO
|
max_age, // TODO
|
||||||
response_mode,
|
response_mode,
|
||||||
redirect_uri,
|
redirect_uri,
|
||||||
created_at: self.grant_created_at,
|
created_at: self.grant_created_at,
|
||||||
@ -340,6 +348,9 @@ pub async fn get_grant_by_id(
|
|||||||
ON usa.session_id = us.id
|
ON usa.session_id = us.id
|
||||||
WHERE
|
WHERE
|
||||||
og.id = $1
|
og.id = $1
|
||||||
|
|
||||||
|
ORDER BY usa.created_at DESC
|
||||||
|
LIMIT 1
|
||||||
"#,
|
"#,
|
||||||
id,
|
id,
|
||||||
)
|
)
|
||||||
@ -399,6 +410,9 @@ pub async fn lookup_grant_by_code(
|
|||||||
ON usa.session_id = us.id
|
ON usa.session_id = us.id
|
||||||
WHERE
|
WHERE
|
||||||
og.code = $1
|
og.code = $1
|
||||||
|
|
||||||
|
ORDER BY usa.created_at DESC
|
||||||
|
LIMIT 1
|
||||||
"#,
|
"#,
|
||||||
code,
|
code,
|
||||||
)
|
)
|
||||||
|
@ -21,7 +21,7 @@ use thiserror::Error;
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use super::{client::Client, session::Session};
|
use super::{client::Client, session::Session};
|
||||||
use crate::traits::StorageBackend;
|
use crate::{traits::StorageBackend, StorageBackendMarker};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||||
pub struct Pkce {
|
pub struct Pkce {
|
||||||
@ -53,7 +53,7 @@ pub struct AuthorizationCode {
|
|||||||
pub struct InvalidTransitionError;
|
pub struct InvalidTransitionError;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
#[serde(bound = "T: StorageBackend")]
|
#[serde(bound = "T: StorageBackend", tag = "stage", rename_all = "lowercase")]
|
||||||
pub enum AuthorizationGrantStage<T: StorageBackend> {
|
pub enum AuthorizationGrantStage<T: StorageBackend> {
|
||||||
Pending,
|
Pending,
|
||||||
Fulfilled {
|
Fulfilled {
|
||||||
@ -117,6 +117,32 @@ impl<T: StorageBackend> AuthorizationGrantStage<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<S: StorageBackendMarker> From<AuthorizationGrantStage<S>> for AuthorizationGrantStage<()> {
|
||||||
|
fn from(s: AuthorizationGrantStage<S>) -> Self {
|
||||||
|
use AuthorizationGrantStage::*;
|
||||||
|
match s {
|
||||||
|
Pending => Pending,
|
||||||
|
Fulfilled {
|
||||||
|
session,
|
||||||
|
fulfilled_at,
|
||||||
|
} => Fulfilled {
|
||||||
|
session: session.into(),
|
||||||
|
fulfilled_at,
|
||||||
|
},
|
||||||
|
Exchanged {
|
||||||
|
session,
|
||||||
|
fulfilled_at,
|
||||||
|
exchanged_at,
|
||||||
|
} => Exchanged {
|
||||||
|
session: session.into(),
|
||||||
|
fulfilled_at,
|
||||||
|
exchanged_at,
|
||||||
|
},
|
||||||
|
Cancelled { cancelled_at } => Cancelled { cancelled_at },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
#[serde(bound = "T: StorageBackend")]
|
#[serde(bound = "T: StorageBackend")]
|
||||||
pub struct AuthorizationGrant<T: StorageBackend> {
|
pub struct AuthorizationGrant<T: StorageBackend> {
|
||||||
@ -138,9 +164,30 @@ pub struct AuthorizationGrant<T: StorageBackend> {
|
|||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<S: StorageBackendMarker> From<AuthorizationGrant<S>> for AuthorizationGrant<()> {
|
||||||
|
fn from(g: AuthorizationGrant<S>) -> Self {
|
||||||
|
AuthorizationGrant {
|
||||||
|
data: (),
|
||||||
|
stage: g.stage.into(),
|
||||||
|
code: g.code,
|
||||||
|
client: g.client.into(),
|
||||||
|
redirect_uri: g.redirect_uri,
|
||||||
|
scope: g.scope,
|
||||||
|
state: g.state,
|
||||||
|
nonce: g.nonce,
|
||||||
|
max_age: g.max_age,
|
||||||
|
acr_values: g.acr_values,
|
||||||
|
response_mode: g.response_mode,
|
||||||
|
response_type_token: g.response_type_token,
|
||||||
|
response_type_id_token: g.response_type_id_token,
|
||||||
|
created_at: g.created_at,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: StorageBackend> AuthorizationGrant<T> {
|
impl<T: StorageBackend> AuthorizationGrant<T> {
|
||||||
pub fn max_auth_time(&self) -> DateTime<Utc> {
|
pub fn max_auth_time(&self) -> DateTime<Utc> {
|
||||||
let max_age: Option<i64> = self.max_age.map(|x| x.get().into());
|
let max_age: Option<i64> = self.max_age.map(|x| x.get().into());
|
||||||
self.created_at + Duration::seconds(max_age.unwrap_or(3600 * 24 * 365))
|
self.created_at - Duration::seconds(max_age.unwrap_or(3600 * 24 * 365))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,15 +12,15 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use std::{collections::HashSet, hash::Hash};
|
use std::{collections::HashSet, hash::Hash, num::NonZeroU32};
|
||||||
|
|
||||||
use chrono::{DateTime, Duration, Utc};
|
use chrono::{DateTime, Duration, Utc};
|
||||||
use language_tags::LanguageTag;
|
use language_tags::LanguageTag;
|
||||||
use parse_display::{Display, FromStr};
|
use parse_display::{Display, FromStr};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::{
|
use serde_with::{
|
||||||
rust::StringWithSeparator, serde_as, skip_serializing_none, DurationSeconds, SpaceSeparator,
|
rust::StringWithSeparator, serde_as, skip_serializing_none, DisplayFromStr, DurationSeconds,
|
||||||
TimestampSeconds,
|
SpaceSeparator, TimestampSeconds,
|
||||||
};
|
};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
@ -168,9 +168,9 @@ pub struct AuthorizationRequest {
|
|||||||
|
|
||||||
display: Option<Display>,
|
display: Option<Display>,
|
||||||
|
|
||||||
#[serde_as(as = "Option<DurationSeconds<i64>>")]
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub max_age: Option<Duration>,
|
#[serde_as(as = "Option<DisplayFromStr>")]
|
||||||
|
pub max_age: Option<NonZeroU32>,
|
||||||
|
|
||||||
#[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, LanguageTag>>")]
|
#[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, LanguageTag>>")]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
//! Contexts used in templates
|
//! Contexts used in templates
|
||||||
|
|
||||||
use mas_data_model::{errors::ErroredForm, BrowserSession, StorageBackend};
|
use mas_data_model::{errors::ErroredForm, AuthorizationGrant, BrowserSession, StorageBackend};
|
||||||
use oauth2_types::errors::OAuth2Error;
|
use oauth2_types::errors::OAuth2Error;
|
||||||
use serde::{ser::SerializeStruct, Serialize};
|
use serde::{ser::SerializeStruct, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@ -210,10 +210,18 @@ pub enum LoginFormField {
|
|||||||
Password,
|
Password,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Context used in login and reauth screens, for the post-auth action to do
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
|
pub enum PostAuthContext {
|
||||||
|
ContinueAuthorizationGrant { grant: AuthorizationGrant<()> },
|
||||||
|
}
|
||||||
|
|
||||||
/// Context used by the `login.html` template
|
/// Context used by the `login.html` template
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct LoginContext {
|
pub struct LoginContext {
|
||||||
form: ErroredForm<LoginFormField>,
|
form: ErroredForm<LoginFormField>,
|
||||||
|
next: Option<PostAuthContext>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TemplateContext for LoginContext {
|
impl TemplateContext for LoginContext {
|
||||||
@ -224,14 +232,23 @@ impl TemplateContext for LoginContext {
|
|||||||
// TODO: samples with errors
|
// TODO: samples with errors
|
||||||
vec![LoginContext {
|
vec![LoginContext {
|
||||||
form: ErroredForm::default(),
|
form: ErroredForm::default(),
|
||||||
|
next: None,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LoginContext {
|
impl LoginContext {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with_form_error(form: ErroredForm<LoginFormField>) -> Self {
|
pub fn with_form_error(self, form: ErroredForm<LoginFormField>) -> Self {
|
||||||
Self { form }
|
Self { form, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_post_action(self, next: PostAuthContext) -> Self {
|
||||||
|
Self {
|
||||||
|
next: Some(next),
|
||||||
|
..self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,10 +256,61 @@ impl Default for LoginContext {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
form: ErroredForm::new(),
|
form: ErroredForm::new(),
|
||||||
|
next: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum ReauthFormField {
|
||||||
|
Password,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TemplateContext for ReauthContext {
|
||||||
|
fn sample() -> Vec<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
// TODO: samples with errors
|
||||||
|
vec![ReauthContext {
|
||||||
|
form: ErroredForm::default(),
|
||||||
|
next: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReauthContext {
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_form_error(self, form: ErroredForm<ReauthFormField>) -> Self {
|
||||||
|
Self { form, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_post_action(self, next: PostAuthContext) -> Self {
|
||||||
|
Self {
|
||||||
|
next: Some(next),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ReauthContext {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
form: ErroredForm::new(),
|
||||||
|
next: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Context used by the `reauth.html` template
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ReauthContext {
|
||||||
|
form: ErroredForm<ReauthFormField>,
|
||||||
|
next: Option<PostAuthContext>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Context used by the `form_post.html` template
|
/// Context used by the `form_post.html` template
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct FormPostContext<T> {
|
pub struct FormPostContext<T> {
|
||||||
|
@ -41,7 +41,8 @@ mod macros;
|
|||||||
|
|
||||||
pub use self::context::{
|
pub use self::context::{
|
||||||
EmptyContext, ErrorContext, FormPostContext, IndexContext, LoginContext, LoginFormField,
|
EmptyContext, ErrorContext, FormPostContext, IndexContext, LoginContext, LoginFormField,
|
||||||
TemplateContext, WithCsrf, WithOptionalSession, WithSession,
|
PostAuthContext, ReauthContext, ReauthFormField, TemplateContext, WithCsrf,
|
||||||
|
WithOptionalSession, WithSession,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Wrapper around [`tera::Tera`] helping rendering the various templates
|
/// Wrapper around [`tera::Tera`] helping rendering the various templates
|
||||||
@ -202,7 +203,7 @@ register_templates! {
|
|||||||
pub fn render_index(WithCsrf<WithOptionalSession<IndexContext>>) { "index.html" }
|
pub fn render_index(WithCsrf<WithOptionalSession<IndexContext>>) { "index.html" }
|
||||||
|
|
||||||
/// Render the re-authentication form
|
/// Render the re-authentication form
|
||||||
pub fn render_reauth(WithCsrf<WithSession<EmptyContext>>) { "reauth.html" }
|
pub fn render_reauth(WithCsrf<WithSession<ReauthContext>>) { "reauth.html" }
|
||||||
|
|
||||||
/// Render the form used by the form_post response mode
|
/// Render the form used by the form_post response mode
|
||||||
pub fn render_form_post<T: Serialize>(FormPostContext<T>) { "form_post.html" }
|
pub fn render_form_post<T: Serialize>(FormPostContext<T>) { "form_post.html" }
|
||||||
|
@ -20,7 +20,7 @@ limitations under the License.
|
|||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container is-max-desktop">
|
<div class="container is-max-desktop">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-half is-offset-one-quarter">
|
<div class="column is-one-third">
|
||||||
{% if form.has_errors %}
|
{% if form.has_errors %}
|
||||||
<article class="message is-danger">
|
<article class="message is-danger">
|
||||||
<div class="message-body">
|
<div class="message-body">
|
||||||
@ -64,6 +64,15 @@ limitations under the License.
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if next %}
|
||||||
|
<div class="column is-two-third">
|
||||||
|
<div class="block">
|
||||||
|
<h3 class="title is-3">Next action:</h3>
|
||||||
|
<pre><code>{{ next | json_encode(pretty=True) | safe }}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -36,7 +36,16 @@ limitations under the License.
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-two-third">
|
<div class="column is-two-third">
|
||||||
<pre><code>{{ current_session | json_encode(pretty=True) | safe }}</code></pre>
|
<div class="block">
|
||||||
|
<h3 class="title is-3">Current session data:</h3>
|
||||||
|
<pre><code>{{ current_session | json_encode(pretty=True) | safe }}</code></pre>
|
||||||
|
</div>
|
||||||
|
{% if next %}
|
||||||
|
<div class="block">
|
||||||
|
<h3 class="title is-3">Next action:</h3>
|
||||||
|
<pre><code>{{ next | json_encode(pretty=True) | safe }}</code></pre>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user