diff --git a/.sqlx/query-d5ec5d1952918c1d6ca035446cc5ffb805f271d621116b3ab314a1c57e3ba5c3.json b/.sqlx/query-00cb951e3b8fcb33fd16a4f1ebfc1a6298c7068d891e0c67816e3db077953736.json similarity index 75% rename from .sqlx/query-d5ec5d1952918c1d6ca035446cc5ffb805f271d621116b3ab314a1c57e3ba5c3.json rename to .sqlx/query-00cb951e3b8fcb33fd16a4f1ebfc1a6298c7068d891e0c67816e3db077953736.json index 0ed7bd5..cefb0aa 100644 --- a/.sqlx/query-d5ec5d1952918c1d6ca035446cc5ffb805f271d621116b3ab314a1c57e3ba5c3.json +++ b/.sqlx/query-00cb951e3b8fcb33fd16a4f1ebfc1a6298c7068d891e0c67816e3db077953736.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT did, device_id, client_id, client_auth, parameters, expires_at, code\n FROM oauth_authorization_request\n WHERE id = $1\n ", + "query": "\n SELECT did, device_id, client_id, client_auth, parameters, expires_at, code, controller_did\n FROM oauth_authorization_request\n WHERE id = $1\n ", "describe": { "columns": [ { @@ -37,6 +37,11 @@ "ordinal": 6, "name": "code", "type_info": "Text" + }, + { + "ordinal": 7, + "name": "controller_did", + "type_info": "Text" } ], "parameters": { @@ -51,8 +56,9 @@ true, false, false, + true, true ] }, - "hash": "d5ec5d1952918c1d6ca035446cc5ffb805f271d621116b3ab314a1c57e3ba5c3" + "hash": "00cb951e3b8fcb33fd16a4f1ebfc1a6298c7068d891e0c67816e3db077953736" } diff --git a/.sqlx/query-03e943475fd0af07d3e1ed5c14276c7841af9fc59076bd4017742844a91d29a1.json b/.sqlx/query-03e943475fd0af07d3e1ed5c14276c7841af9fc59076bd4017742844a91d29a1.json new file mode 100644 index 0000000..c3b6945 --- /dev/null +++ b/.sqlx/query-03e943475fd0af07d3e1ed5c14276c7841af9fc59076bd4017742844a91d29a1.json @@ -0,0 +1,46 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n u.did,\n u.handle,\n d.granted_scopes,\n d.granted_at,\n (u.deactivated_at IS NULL AND u.takedown_ref IS NULL) as \"is_active!\"\n FROM account_delegations d\n JOIN users u ON u.did = d.controller_did\n WHERE d.delegated_did = $1 AND d.revoked_at IS NULL\n ORDER BY d.granted_at DESC\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "did", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "handle", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "granted_scopes", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "granted_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "is_active!", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + null + ] + }, + "hash": "03e943475fd0af07d3e1ed5c14276c7841af9fc59076bd4017742844a91d29a1" +} diff --git a/.sqlx/query-045ba5a6ab497737d09367f57df825f7945bb317b76b770ef68aa3f53df284a2.json b/.sqlx/query-045ba5a6ab497737d09367f57df825f7945bb317b76b770ef68aa3f53df284a2.json new file mode 100644 index 0000000..7eac88c --- /dev/null +++ b/.sqlx/query-045ba5a6ab497737d09367f57df825f7945bb317b76b770ef68aa3f53df284a2.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE oauth_authorization_request\n SET controller_did = $2\n WHERE id = $1\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "045ba5a6ab497737d09367f57df825f7945bb317b76b770ef68aa3f53df284a2" +} diff --git a/.sqlx/query-fd291f783059a00c2ac29920bcb5f12a0553148d8a216eb21dd0e63d5a4b1913.json b/.sqlx/query-06c00269b11c250e85bde385e18ae8df6b1cc122f584105a8ea98861ff89e1b9.json similarity index 83% rename from .sqlx/query-fd291f783059a00c2ac29920bcb5f12a0553148d8a216eb21dd0e63d5a4b1913.json rename to .sqlx/query-06c00269b11c250e85bde385e18ae8df6b1cc122f584105a8ea98861ff89e1b9.json index c1da7a4..b8e499f 100644 --- a/.sqlx/query-fd291f783059a00c2ac29920bcb5f12a0553148d8a216eb21dd0e63d5a4b1913.json +++ b/.sqlx/query-06c00269b11c250e85bde385e18ae8df6b1cc122f584105a8ea98861ff89e1b9.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id, did, token_id, created_at, updated_at, expires_at, client_id, client_auth,\n device_id, parameters, details, code, current_refresh_token, scope\n FROM oauth_token\n WHERE previous_refresh_token = $1 AND rotated_at > $2\n ", + "query": "\n SELECT id, did, token_id, created_at, updated_at, expires_at, client_id, client_auth,\n device_id, parameters, details, code, current_refresh_token, scope, controller_did\n FROM oauth_token\n WHERE previous_refresh_token = $1 AND rotated_at > $2\n ", "describe": { "columns": [ { @@ -72,6 +72,11 @@ "ordinal": 13, "name": "scope", "type_info": "Text" + }, + { + "ordinal": 14, + "name": "controller_did", + "type_info": "Text" } ], "parameters": { @@ -94,8 +99,9 @@ true, true, true, + true, true ] }, - "hash": "fd291f783059a00c2ac29920bcb5f12a0553148d8a216eb21dd0e63d5a4b1913" + "hash": "06c00269b11c250e85bde385e18ae8df6b1cc122f584105a8ea98861ff89e1b9" } diff --git a/.sqlx/query-bc816a96fa2e186cd0ff279f98543bebd9a815677d86fa8852f51fe76f95ce95.json b/.sqlx/query-09cc26fbdc2d210146dccc3f9d1e6e82814596eadfd20d814e9f0d3f615127a8.json similarity index 83% rename from .sqlx/query-bc816a96fa2e186cd0ff279f98543bebd9a815677d86fa8852f51fe76f95ce95.json rename to .sqlx/query-09cc26fbdc2d210146dccc3f9d1e6e82814596eadfd20d814e9f0d3f615127a8.json index d377c3e..5515a49 100644 --- a/.sqlx/query-bc816a96fa2e186cd0ff279f98543bebd9a815677d86fa8852f51fe76f95ce95.json +++ b/.sqlx/query-09cc26fbdc2d210146dccc3f9d1e6e82814596eadfd20d814e9f0d3f615127a8.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id, did, token_id, created_at, updated_at, expires_at, client_id, client_auth,\n device_id, parameters, details, code, current_refresh_token, scope\n FROM oauth_token\n WHERE current_refresh_token = $1\n ", + "query": "\n SELECT id, did, token_id, created_at, updated_at, expires_at, client_id, client_auth,\n device_id, parameters, details, code, current_refresh_token, scope, controller_did\n FROM oauth_token\n WHERE current_refresh_token = $1\n ", "describe": { "columns": [ { @@ -72,6 +72,11 @@ "ordinal": 13, "name": "scope", "type_info": "Text" + }, + { + "ordinal": 14, + "name": "controller_did", + "type_info": "Text" } ], "parameters": { @@ -93,8 +98,9 @@ true, true, true, + true, true ] }, - "hash": "bc816a96fa2e186cd0ff279f98543bebd9a815677d86fa8852f51fe76f95ce95" + "hash": "09cc26fbdc2d210146dccc3f9d1e6e82814596eadfd20d814e9f0d3f615127a8" } diff --git a/.sqlx/query-1a156f5dd3deb0681f7f631321bae44c099eb2eb5d9d1337d22782fe73691a7b.json b/.sqlx/query-1a156f5dd3deb0681f7f631321bae44c099eb2eb5d9d1337d22782fe73691a7b.json new file mode 100644 index 0000000..cd6ad93 --- /dev/null +++ b/.sqlx/query-1a156f5dd3deb0681f7f631321bae44c099eb2eb5d9d1337d22782fe73691a7b.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT password_hash, scopes, created_by_controller_did FROM app_passwords WHERE user_id = $1 ORDER BY created_at DESC LIMIT 20", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "password_hash", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "scopes", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "created_by_controller_did", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + false, + true, + true + ] + }, + "hash": "1a156f5dd3deb0681f7f631321bae44c099eb2eb5d9d1337d22782fe73691a7b" +} diff --git a/.sqlx/query-1f44c06434b913554e26ad1e2674c56701f43fe12907594325e885c6f256045e.json b/.sqlx/query-1f44c06434b913554e26ad1e2674c56701f43fe12907594325e885c6f256045e.json new file mode 100644 index 0000000..fef2aa5 --- /dev/null +++ b/.sqlx/query-1f44c06434b913554e26ad1e2674c56701f43fe12907594325e885c6f256045e.json @@ -0,0 +1,87 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n id,\n delegated_did,\n actor_did,\n controller_did,\n action_type as \"action_type: DelegationActionType\",\n action_details,\n ip_address,\n user_agent,\n created_at\n FROM delegation_audit_log\n WHERE controller_did = $1\n ORDER BY created_at DESC\n LIMIT $2 OFFSET $3\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "delegated_did", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "actor_did", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "controller_did", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "action_type: DelegationActionType", + "type_info": { + "Custom": { + "name": "delegation_action_type", + "kind": { + "Enum": [ + "grant_created", + "grant_revoked", + "scopes_modified", + "token_issued", + "repo_write", + "blob_upload", + "account_action" + ] + } + } + } + }, + { + "ordinal": 5, + "name": "action_details", + "type_info": "Jsonb" + }, + { + "ordinal": 6, + "name": "ip_address", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "user_agent", + "type_info": "Text" + }, + { + "ordinal": 8, + "name": "created_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Text", + "Int8", + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + true, + false, + true, + true, + true, + false + ] + }, + "hash": "1f44c06434b913554e26ad1e2674c56701f43fe12907594325e885c6f256045e" +} diff --git a/.sqlx/query-33d3ad8e4668b029a3cccfac6dda6d4612e248886fd6290aa47253c6bb325c45.json b/.sqlx/query-33d3ad8e4668b029a3cccfac6dda6d4612e248886fd6290aa47253c6bb325c45.json new file mode 100644 index 0000000..097f976 --- /dev/null +++ b/.sqlx/query-33d3ad8e4668b029a3cccfac6dda6d4612e248886fd6290aa47253c6bb325c45.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT COUNT(*) as \"count!\"\n FROM account_delegations d\n JOIN users u ON u.did = d.controller_did\n WHERE d.delegated_did = $1\n AND d.revoked_at IS NULL\n AND u.deactivated_at IS NULL\n AND u.takedown_ref IS NULL\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count!", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "33d3ad8e4668b029a3cccfac6dda6d4612e248886fd6290aa47253c6bb325c45" +} diff --git a/.sqlx/query-3781704482d019cbc5811ceab0ff26749d8fca1b13dfa7b2b2c42273ebb5beed.json b/.sqlx/query-3781704482d019cbc5811ceab0ff26749d8fca1b13dfa7b2b2c42273ebb5beed.json new file mode 100644 index 0000000..c6bed7f --- /dev/null +++ b/.sqlx/query-3781704482d019cbc5811ceab0ff26749d8fca1b13dfa7b2b2c42273ebb5beed.json @@ -0,0 +1,25 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO account_delegations (delegated_did, controller_did, granted_scopes, granted_by)\n VALUES ($1, $2, $3, $4)\n RETURNING id\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Text", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "3781704482d019cbc5811ceab0ff26749d8fca1b13dfa7b2b2c42273ebb5beed" +} diff --git a/.sqlx/query-38154ef1114e42ff2718ab5aa10a653f32d097976d2c4881676d27454ad1c2e5.json b/.sqlx/query-38154ef1114e42ff2718ab5aa10a653f32d097976d2c4881676d27454ad1c2e5.json new file mode 100644 index 0000000..01f389b --- /dev/null +++ b/.sqlx/query-38154ef1114e42ff2718ab5aa10a653f32d097976d2c4881676d27454ad1c2e5.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) as \"count!\" FROM delegation_audit_log WHERE delegated_did = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count!", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "38154ef1114e42ff2718ab5aa10a653f32d097976d2c4881676d27454ad1c2e5" +} diff --git a/.sqlx/query-40d42ed61a77074b298539e492d8fb6493174a7c49324e6f4f20b68bc30e95f4.json b/.sqlx/query-40d42ed61a77074b298539e492d8fb6493174a7c49324e6f4f20b68bc30e95f4.json new file mode 100644 index 0000000..6cd3fde --- /dev/null +++ b/.sqlx/query-40d42ed61a77074b298539e492d8fb6493174a7c49324e6f4f20b68bc30e95f4.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT account_type::text = 'delegated' as \"is_delegated!\" FROM users WHERE did = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "is_delegated!", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "40d42ed61a77074b298539e492d8fb6493174a7c49324e6f4f20b68bc30e95f4" +} diff --git a/.sqlx/query-49e7d9a260209502aa79ef9f83bed78ec38b6f7c068fdf8433696082cfad91a8.json b/.sqlx/query-49e7d9a260209502aa79ef9f83bed78ec38b6f7c068fdf8433696082cfad91a8.json new file mode 100644 index 0000000..3dd0b95 --- /dev/null +++ b/.sqlx/query-49e7d9a260209502aa79ef9f83bed78ec38b6f7c068fdf8433696082cfad91a8.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT EXISTS(\n SELECT 1 FROM account_delegations\n WHERE controller_did = $1 AND revoked_at IS NULL\n ) as \"exists!\"", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "exists!", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "49e7d9a260209502aa79ef9f83bed78ec38b6f7c068fdf8433696082cfad91a8" +} diff --git a/.sqlx/query-df7b49e30dd3388a7f0e6e8b531f0bf15f52cf6e943f7fe74382ac8090a3caf4.json b/.sqlx/query-747a6f19cf9d6e971d359d8d269fe2e50e2ed3682c0bb746e7b2fbc5e493027a.json similarity index 79% rename from .sqlx/query-df7b49e30dd3388a7f0e6e8b531f0bf15f52cf6e943f7fe74382ac8090a3caf4.json rename to .sqlx/query-747a6f19cf9d6e971d359d8d269fe2e50e2ed3682c0bb746e7b2fbc5e493027a.json index bb9eccb..bc59e76 100644 --- a/.sqlx/query-df7b49e30dd3388a7f0e6e8b531f0bf15f52cf6e943f7fe74382ac8090a3caf4.json +++ b/.sqlx/query-747a6f19cf9d6e971d359d8d269fe2e50e2ed3682c0bb746e7b2fbc5e493027a.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n DELETE FROM oauth_authorization_request\n WHERE code = $1\n RETURNING did, device_id, client_id, client_auth, parameters, expires_at, code\n ", + "query": "\n DELETE FROM oauth_authorization_request\n WHERE code = $1\n RETURNING did, device_id, client_id, client_auth, parameters, expires_at, code, controller_did\n ", "describe": { "columns": [ { @@ -37,6 +37,11 @@ "ordinal": 6, "name": "code", "type_info": "Text" + }, + { + "ordinal": 7, + "name": "controller_did", + "type_info": "Text" } ], "parameters": { @@ -51,8 +56,9 @@ true, false, false, + true, true ] }, - "hash": "df7b49e30dd3388a7f0e6e8b531f0bf15f52cf6e943f7fe74382ac8090a3caf4" + "hash": "747a6f19cf9d6e971d359d8d269fe2e50e2ed3682c0bb746e7b2fbc5e493027a" } diff --git a/.sqlx/query-7b4977eb51715a385cb00ee88dd3395fa28f9c0d2edc3dc1670c415ad983394f.json b/.sqlx/query-7b4977eb51715a385cb00ee88dd3395fa28f9c0d2edc3dc1670c415ad983394f.json new file mode 100644 index 0000000..ac1ff4d --- /dev/null +++ b/.sqlx/query-7b4977eb51715a385cb00ee88dd3395fa28f9c0d2edc3dc1670c415ad983394f.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE oauth_authorization_request\n SET did = $2\n WHERE id = $1\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "7b4977eb51715a385cb00ee88dd3395fa28f9c0d2edc3dc1670c415ad983394f" +} diff --git a/.sqlx/query-8023c93fa18592cc5ebde7ae856effa70ef57e2801ecba999512f1b12000de9c.json b/.sqlx/query-8023c93fa18592cc5ebde7ae856effa70ef57e2801ecba999512f1b12000de9c.json new file mode 100644 index 0000000..212f134 --- /dev/null +++ b/.sqlx/query-8023c93fa18592cc5ebde7ae856effa70ef57e2801ecba999512f1b12000de9c.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT EXISTS(SELECT 1 FROM users WHERE did = $1) as \"exists!\"", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "exists!", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "8023c93fa18592cc5ebde7ae856effa70ef57e2801ecba999512f1b12000de9c" +} diff --git a/.sqlx/query-6b0245cefaec65a48c51239ed099e45c5347224c81f7d01d7af5bd7664d16883.json b/.sqlx/query-80c029ff08ef3f7d19054fca573dee4037f38b7a7bf1473a0cae7887350de556.json similarity index 57% rename from .sqlx/query-6b0245cefaec65a48c51239ed099e45c5347224c81f7d01d7af5bd7664d16883.json rename to .sqlx/query-80c029ff08ef3f7d19054fca573dee4037f38b7a7bf1473a0cae7887350de556.json index f9b3500..854f6b1 100644 --- a/.sqlx/query-6b0245cefaec65a48c51239ed099e45c5347224c81f7d01d7af5bd7664d16883.json +++ b/.sqlx/query-80c029ff08ef3f7d19054fca573dee4037f38b7a7bf1473a0cae7887350de556.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT st.id, st.did, st.scope, k.key_bytes, k.encryption_version\n FROM session_tokens st\n JOIN users u ON st.did = u.did\n JOIN user_keys k ON u.id = k.user_id\n WHERE st.refresh_jti = $1 AND st.refresh_expires_at > NOW()\n FOR UPDATE OF st", + "query": "SELECT st.id, st.did, st.scope, st.controller_did, k.key_bytes, k.encryption_version\n FROM session_tokens st\n JOIN users u ON st.did = u.did\n JOIN user_keys k ON u.id = k.user_id\n WHERE st.refresh_jti = $1 AND st.refresh_expires_at > NOW()\n FOR UPDATE OF st", "describe": { "columns": [ { @@ -20,11 +20,16 @@ }, { "ordinal": 3, + "name": "controller_did", + "type_info": "Text" + }, + { + "ordinal": 4, "name": "key_bytes", "type_info": "Bytea" }, { - "ordinal": 4, + "ordinal": 5, "name": "encryption_version", "type_info": "Int4" } @@ -38,9 +43,10 @@ false, false, true, + true, false, true ] }, - "hash": "6b0245cefaec65a48c51239ed099e45c5347224c81f7d01d7af5bd7664d16883" + "hash": "80c029ff08ef3f7d19054fca573dee4037f38b7a7bf1473a0cae7887350de556" } diff --git a/.sqlx/query-90f46f595f418c306a9229e5c5379bb6e1a3f121a346dce565e6d3075b058f01.json b/.sqlx/query-90f46f595f418c306a9229e5c5379bb6e1a3f121a346dce565e6d3075b058f01.json new file mode 100644 index 0000000..c9c7f8a --- /dev/null +++ b/.sqlx/query-90f46f595f418c306a9229e5c5379bb6e1a3f121a346dce565e6d3075b058f01.json @@ -0,0 +1,70 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT id, did, password_hash, deactivated_at, takedown_ref,\n email_verified, discord_verified, telegram_verified, signal_verified\n FROM users\n WHERE did = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "did", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "password_hash", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "deactivated_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "takedown_ref", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "email_verified", + "type_info": "Bool" + }, + { + "ordinal": 6, + "name": "discord_verified", + "type_info": "Bool" + }, + { + "ordinal": 7, + "name": "telegram_verified", + "type_info": "Bool" + }, + { + "ordinal": 8, + "name": "signal_verified", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + true, + true, + true, + false, + false, + false, + false + ] + }, + "hash": "90f46f595f418c306a9229e5c5379bb6e1a3f121a346dce565e6d3075b058f01" +} diff --git a/.sqlx/query-9182105a0f3cd4659e7c4bedb13c5670121fb25c351aa427a6b42a632c95e249.json b/.sqlx/query-9182105a0f3cd4659e7c4bedb13c5670121fb25c351aa427a6b42a632c95e249.json new file mode 100644 index 0000000..309af18 --- /dev/null +++ b/.sqlx/query-9182105a0f3cd4659e7c4bedb13c5670121fb25c351aa427a6b42a632c95e249.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO account_delegations (delegated_did, controller_did, granted_scopes, granted_by)\n VALUES ($1, $2, $3, $4)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "9182105a0f3cd4659e7c4bedb13c5670121fb25c351aa427a6b42a632c95e249" +} diff --git a/.sqlx/query-53d124a7cbdf5e121a3469f82225fa9ec69fb74c3fbf335be6ca76ecf9c16765.json b/.sqlx/query-a886fcf853e54f3be88143b373f58a7fbf0881d19649c036660ef6cf52d14fa2.json similarity index 83% rename from .sqlx/query-53d124a7cbdf5e121a3469f82225fa9ec69fb74c3fbf335be6ca76ecf9c16765.json rename to .sqlx/query-a886fcf853e54f3be88143b373f58a7fbf0881d19649c036660ef6cf52d14fa2.json index 67fb8f0..15fc438 100644 --- a/.sqlx/query-53d124a7cbdf5e121a3469f82225fa9ec69fb74c3fbf335be6ca76ecf9c16765.json +++ b/.sqlx/query-a886fcf853e54f3be88143b373f58a7fbf0881d19649c036660ef6cf52d14fa2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT did, token_id, created_at, updated_at, expires_at, client_id, client_auth,\n device_id, parameters, details, code, current_refresh_token, scope\n FROM oauth_token\n WHERE did = $1\n ", + "query": "\n SELECT did, token_id, created_at, updated_at, expires_at, client_id, client_auth,\n device_id, parameters, details, code, current_refresh_token, scope, controller_did\n FROM oauth_token\n WHERE token_id = $1\n ", "describe": { "columns": [ { @@ -67,6 +67,11 @@ "ordinal": 12, "name": "scope", "type_info": "Text" + }, + { + "ordinal": 13, + "name": "controller_did", + "type_info": "Text" } ], "parameters": { @@ -87,8 +92,9 @@ true, true, true, + true, true ] }, - "hash": "53d124a7cbdf5e121a3469f82225fa9ec69fb74c3fbf335be6ca76ecf9c16765" + "hash": "a886fcf853e54f3be88143b373f58a7fbf0881d19649c036660ef6cf52d14fa2" } diff --git a/.sqlx/query-a9bf34b436e0eecf3489cdabe9286b4ecb18905dc66e86a4084081f943b71d4c.json b/.sqlx/query-a9bf34b436e0eecf3489cdabe9286b4ecb18905dc66e86a4084081f943b71d4c.json new file mode 100644 index 0000000..4c5eec1 --- /dev/null +++ b/.sqlx/query-a9bf34b436e0eecf3489cdabe9286b4ecb18905dc66e86a4084081f943b71d4c.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT EXISTS(\n SELECT 1 FROM account_delegations\n WHERE delegated_did = $1 AND revoked_at IS NULL\n ) as \"exists!\"", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "exists!", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "a9bf34b436e0eecf3489cdabe9286b4ecb18905dc66e86a4084081f943b71d4c" +} diff --git a/.sqlx/query-b5d3a6a68443fbf3e6027f462ffaf5ac7e0d44344ce181e5a81932e7610265c8.json b/.sqlx/query-b474591bf3bd9359bd0d8af186f090a32c79a940771168d67160f3190da2eea4.json similarity index 84% rename from .sqlx/query-b5d3a6a68443fbf3e6027f462ffaf5ac7e0d44344ce181e5a81932e7610265c8.json rename to .sqlx/query-b474591bf3bd9359bd0d8af186f090a32c79a940771168d67160f3190da2eea4.json index af03b5d..b60cc75 100644 --- a/.sqlx/query-b5d3a6a68443fbf3e6027f462ffaf5ac7e0d44344ce181e5a81932e7610265c8.json +++ b/.sqlx/query-b474591bf3bd9359bd0d8af186f090a32c79a940771168d67160f3190da2eea4.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT did, token_id, created_at, updated_at, expires_at, client_id, client_auth,\n device_id, parameters, details, code, current_refresh_token, scope\n FROM oauth_token\n WHERE token_id = $1\n ", + "query": "\n SELECT did, token_id, created_at, updated_at, expires_at, client_id, client_auth,\n device_id, parameters, details, code, current_refresh_token, scope, controller_did\n FROM oauth_token\n WHERE did = $1\n ", "describe": { "columns": [ { @@ -67,6 +67,11 @@ "ordinal": 12, "name": "scope", "type_info": "Text" + }, + { + "ordinal": 13, + "name": "controller_did", + "type_info": "Text" } ], "parameters": { @@ -87,8 +92,9 @@ true, true, true, + true, true ] }, - "hash": "b5d3a6a68443fbf3e6027f462ffaf5ac7e0d44344ce181e5a81932e7610265c8" + "hash": "b474591bf3bd9359bd0d8af186f090a32c79a940771168d67160f3190da2eea4" } diff --git a/.sqlx/query-f47f2236dcc27bc203b0cd13cc022611492f0f82c572c5a536663e8d252cfafb.json b/.sqlx/query-bbd387655387724e97f819e78033682edffbd2463a65b2bb48ca73794dafdbcc.json similarity index 63% rename from .sqlx/query-f47f2236dcc27bc203b0cd13cc022611492f0f82c572c5a536663e8d252cfafb.json rename to .sqlx/query-bbd387655387724e97f819e78033682edffbd2463a65b2bb48ca73794dafdbcc.json index 93f1ba5..d7fcfd2 100644 --- a/.sqlx/query-f47f2236dcc27bc203b0cd13cc022611492f0f82c572c5a536663e8d252cfafb.json +++ b/.sqlx/query-bbd387655387724e97f819e78033682edffbd2463a65b2bb48ca73794dafdbcc.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT name, created_at, privileged, scopes FROM app_passwords WHERE user_id = $1 ORDER BY created_at DESC", + "query": "SELECT name, created_at, privileged, scopes, created_by_controller_did FROM app_passwords WHERE user_id = $1 ORDER BY created_at DESC", "describe": { "columns": [ { @@ -22,6 +22,11 @@ "ordinal": 3, "name": "scopes", "type_info": "Text" + }, + { + "ordinal": 4, + "name": "created_by_controller_did", + "type_info": "Text" } ], "parameters": { @@ -33,8 +38,9 @@ false, false, false, + true, true ] }, - "hash": "f47f2236dcc27bc203b0cd13cc022611492f0f82c572c5a536663e8d252cfafb" + "hash": "bbd387655387724e97f819e78033682edffbd2463a65b2bb48ca73794dafdbcc" } diff --git a/.sqlx/query-bc0d078c738c6ebdaa19446608e96727c0f2f227e9fbcb06172e5c444bea6347.json b/.sqlx/query-bc0d078c738c6ebdaa19446608e96727c0f2f227e9fbcb06172e5c444bea6347.json new file mode 100644 index 0000000..36f385c --- /dev/null +++ b/.sqlx/query-bc0d078c738c6ebdaa19446608e96727c0f2f227e9fbcb06172e5c444bea6347.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE account_delegations\n SET granted_scopes = $1\n WHERE delegated_did = $2 AND controller_did = $3 AND revoked_at IS NULL\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "bc0d078c738c6ebdaa19446608e96727c0f2f227e9fbcb06172e5c444bea6347" +} diff --git a/.sqlx/query-bc466b477a4ec8374078e9ba38cc735895a52babc75d7e8009baed8e5e843c38.json b/.sqlx/query-bc466b477a4ec8374078e9ba38cc735895a52babc75d7e8009baed8e5e843c38.json new file mode 100644 index 0000000..7fc22b6 --- /dev/null +++ b/.sqlx/query-bc466b477a4ec8374078e9ba38cc735895a52babc75d7e8009baed8e5e843c38.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO session_tokens (did, access_jti, refresh_jti, access_expires_at, refresh_expires_at, legacy_login, mfa_verified, scope, controller_did) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Text", + "Timestamptz", + "Timestamptz", + "Bool", + "Bool", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "bc466b477a4ec8374078e9ba38cc735895a52babc75d7e8009baed8e5e843c38" +} diff --git a/.sqlx/query-c32aa6a95bf31d41eb2c60b97e6a90ae6a3ff84cc48e52459bc8657a7ce36413.json b/.sqlx/query-c32aa6a95bf31d41eb2c60b97e6a90ae6a3ff84cc48e52459bc8657a7ce36413.json new file mode 100644 index 0000000..f6d984d --- /dev/null +++ b/.sqlx/query-c32aa6a95bf31d41eb2c60b97e6a90ae6a3ff84cc48e52459bc8657a7ce36413.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM app_passwords\n WHERE user_id = (SELECT id FROM users WHERE did = $1)\n AND created_by_controller_did = $2\n RETURNING id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "c32aa6a95bf31d41eb2c60b97e6a90ae6a3ff84cc48e52459bc8657a7ce36413" +} diff --git a/.sqlx/query-8d634d6c3306424ed9239f078a4892245f4b73049037ea8f3cf23fc377b57a40.json b/.sqlx/query-c3a0d5bbac7b0d33f79e61fd9790cd737b62628f5597489066228dd30af42c82.json similarity index 61% rename from .sqlx/query-8d634d6c3306424ed9239f078a4892245f4b73049037ea8f3cf23fc377b57a40.json rename to .sqlx/query-c3a0d5bbac7b0d33f79e61fd9790cd737b62628f5597489066228dd30af42c82.json index d8be7b1..d404279 100644 --- a/.sqlx/query-8d634d6c3306424ed9239f078a4892245f4b73049037ea8f3cf23fc377b57a40.json +++ b/.sqlx/query-c3a0d5bbac7b0d33f79e61fd9790cd737b62628f5597489066228dd30af42c82.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO app_passwords (user_id, name, password_hash, created_at, privileged, scopes) VALUES ($1, $2, $3, $4, $5, $6)", + "query": "INSERT INTO app_passwords (user_id, name, password_hash, created_at, privileged, scopes, created_by_controller_did) VALUES ($1, $2, $3, $4, $5, $6, $7)", "describe": { "columns": [], "parameters": { @@ -10,10 +10,11 @@ "Text", "Timestamptz", "Bool", + "Text", "Text" ] }, "nullable": [] }, - "hash": "8d634d6c3306424ed9239f078a4892245f4b73049037ea8f3cf23fc377b57a40" + "hash": "c3a0d5bbac7b0d33f79e61fd9790cd737b62628f5597489066228dd30af42c82" } diff --git a/.sqlx/query-3d5ab47cdcb0d04b0a0d63c2d5a0cc45889ff4330b500ba7e77eac06ee9606c9.json b/.sqlx/query-c4621f6a8a1ab78a6355b09fdfc2bf8999d276564e93015792ec07cb05e79038.json similarity index 59% rename from .sqlx/query-3d5ab47cdcb0d04b0a0d63c2d5a0cc45889ff4330b500ba7e77eac06ee9606c9.json rename to .sqlx/query-c4621f6a8a1ab78a6355b09fdfc2bf8999d276564e93015792ec07cb05e79038.json index 6011891..6a04e0c 100644 --- a/.sqlx/query-3d5ab47cdcb0d04b0a0d63c2d5a0cc45889ff4330b500ba7e77eac06ee9606c9.json +++ b/.sqlx/query-c4621f6a8a1ab78a6355b09fdfc2bf8999d276564e93015792ec07cb05e79038.json @@ -1,22 +1,22 @@ { "db_name": "PostgreSQL", - "query": "SELECT password_hash, scopes FROM app_passwords WHERE user_id = $1 ORDER BY created_at DESC LIMIT 20", + "query": "SELECT did, password_hash FROM users WHERE handle = $1 OR email = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "password_hash", + "name": "did", "type_info": "Text" }, { "ordinal": 1, - "name": "scopes", + "name": "password_hash", "type_info": "Text" } ], "parameters": { "Left": [ - "Uuid" + "Text" ] }, "nullable": [ @@ -24,5 +24,5 @@ true ] }, - "hash": "3d5ab47cdcb0d04b0a0d63c2d5a0cc45889ff4330b500ba7e77eac06ee9606c9" + "hash": "c4621f6a8a1ab78a6355b09fdfc2bf8999d276564e93015792ec07cb05e79038" } diff --git a/.sqlx/query-eeaf29b5efeb08c4729dec89f1e76c817a53bbf99998c5b1e428227d1b223b0f.json b/.sqlx/query-c7353563d686b963723fb049b3a3f9f0162afef510b91926e29cf74ec05d25c6.json similarity index 84% rename from .sqlx/query-eeaf29b5efeb08c4729dec89f1e76c817a53bbf99998c5b1e428227d1b223b0f.json rename to .sqlx/query-c7353563d686b963723fb049b3a3f9f0162afef510b91926e29cf74ec05d25c6.json index 484db6b..7abb8ea 100644 --- a/.sqlx/query-eeaf29b5efeb08c4729dec89f1e76c817a53bbf99998c5b1e428227d1b223b0f.json +++ b/.sqlx/query-c7353563d686b963723fb049b3a3f9f0162afef510b91926e29cf74ec05d25c6.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id, did, email, password_hash, password_required, two_factor_enabled,\n preferred_comms_channel as \"preferred_comms_channel: CommsChannel\",\n deactivated_at, takedown_ref,\n email_verified, discord_verified, telegram_verified, signal_verified\n FROM users\n WHERE handle = $1 OR email = $1\n ", + "query": "\n SELECT id, did, email, password_hash, password_required, two_factor_enabled,\n preferred_comms_channel as \"preferred_comms_channel: CommsChannel\",\n deactivated_at, takedown_ref,\n email_verified, discord_verified, telegram_verified, signal_verified,\n account_type::text as \"account_type!\"\n FROM users\n WHERE handle = $1 OR email = $1\n ", "describe": { "columns": [ { @@ -79,6 +79,11 @@ "ordinal": 12, "name": "signal_verified", "type_info": "Bool" + }, + { + "ordinal": 13, + "name": "account_type!", + "type_info": "Text" } ], "parameters": { @@ -99,8 +104,9 @@ false, false, false, - false + false, + null ] }, - "hash": "eeaf29b5efeb08c4729dec89f1e76c817a53bbf99998c5b1e428227d1b223b0f" + "hash": "c7353563d686b963723fb049b3a3f9f0162afef510b91926e29cf74ec05d25c6" } diff --git a/.sqlx/query-6b30d0a7dc0759c336334c2d34d3302b883795730c5dfa97925319dc998a43f0.json b/.sqlx/query-cd3bc8199c3f9285f214ef091ad52dc881a19cf19fe27a2ba1f383ffb8e3fc0d.json similarity index 73% rename from .sqlx/query-6b30d0a7dc0759c336334c2d34d3302b883795730c5dfa97925319dc998a43f0.json rename to .sqlx/query-cd3bc8199c3f9285f214ef091ad52dc881a19cf19fe27a2ba1f383ffb8e3fc0d.json index 32de36c..42364d8 100644 --- a/.sqlx/query-6b30d0a7dc0759c336334c2d34d3302b883795730c5dfa97925319dc998a43f0.json +++ b/.sqlx/query-cd3bc8199c3f9285f214ef091ad52dc881a19cf19fe27a2ba1f383ffb8e3fc0d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO oauth_token\n (did, token_id, created_at, updated_at, expires_at, client_id, client_auth,\n device_id, parameters, details, code, current_refresh_token, scope)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)\n RETURNING id\n ", + "query": "\n INSERT INTO oauth_token\n (did, token_id, created_at, updated_at, expires_at, client_id, client_auth,\n device_id, parameters, details, code, current_refresh_token, scope, controller_did)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)\n RETURNING id\n ", "describe": { "columns": [ { @@ -23,6 +23,7 @@ "Jsonb", "Text", "Text", + "Text", "Text" ] }, @@ -30,5 +31,5 @@ false ] }, - "hash": "6b30d0a7dc0759c336334c2d34d3302b883795730c5dfa97925319dc998a43f0" + "hash": "cd3bc8199c3f9285f214ef091ad52dc881a19cf19fe27a2ba1f383ffb8e3fc0d" } diff --git a/.sqlx/query-ceb51f40c33d99fc17c37d7cb685152c5f9d447bcbbedd47e8fb34d358e7669a.json b/.sqlx/query-ceb51f40c33d99fc17c37d7cb685152c5f9d447bcbbedd47e8fb34d358e7669a.json new file mode 100644 index 0000000..7a49ed5 --- /dev/null +++ b/.sqlx/query-ceb51f40c33d99fc17c37d7cb685152c5f9d447bcbbedd47e8fb34d358e7669a.json @@ -0,0 +1,40 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n u.did,\n u.handle,\n d.granted_scopes,\n d.granted_at\n FROM account_delegations d\n JOIN users u ON u.did = d.delegated_did\n WHERE d.controller_did = $1\n AND d.revoked_at IS NULL\n AND u.deactivated_at IS NULL\n AND u.takedown_ref IS NULL\n ORDER BY d.granted_at DESC\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "did", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "handle", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "granted_scopes", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "granted_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "ceb51f40c33d99fc17c37d7cb685152c5f9d447bcbbedd47e8fb34d358e7669a" +} diff --git a/.sqlx/query-d8e33a911d741e636d1f0efd81f8fc528d9af2716887d0d72b70ca7c7d7eb11a.json b/.sqlx/query-d8e33a911d741e636d1f0efd81f8fc528d9af2716887d0d72b70ca7c7d7eb11a.json new file mode 100644 index 0000000..20d7088 --- /dev/null +++ b/.sqlx/query-d8e33a911d741e636d1f0efd81f8fc528d9af2716887d0d72b70ca7c7d7eb11a.json @@ -0,0 +1,46 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n u.did,\n u.handle,\n d.granted_scopes,\n d.granted_at,\n true as \"is_active!\"\n FROM account_delegations d\n JOIN users u ON u.did = d.controller_did\n WHERE d.delegated_did = $1\n AND d.revoked_at IS NULL\n AND u.deactivated_at IS NULL\n AND u.takedown_ref IS NULL\n ORDER BY d.granted_at DESC\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "did", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "handle", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "granted_scopes", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "granted_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "is_active!", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + null + ] + }, + "hash": "d8e33a911d741e636d1f0efd81f8fc528d9af2716887d0d72b70ca7c7d7eb11a" +} diff --git a/.sqlx/query-ddd3e85a88d9a782c54bdc33072747dd5db70cf76432e50635e22343092eadeb.json b/.sqlx/query-ddd3e85a88d9a782c54bdc33072747dd5db70cf76432e50635e22343092eadeb.json new file mode 100644 index 0000000..2e599e6 --- /dev/null +++ b/.sqlx/query-ddd3e85a88d9a782c54bdc33072747dd5db70cf76432e50635e22343092eadeb.json @@ -0,0 +1,65 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT id, delegated_did, controller_did, granted_scopes,\n granted_at, granted_by, revoked_at, revoked_by\n FROM account_delegations\n WHERE delegated_did = $1 AND controller_did = $2 AND revoked_at IS NULL\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "delegated_did", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "controller_did", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "granted_scopes", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "granted_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 5, + "name": "granted_by", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "revoked_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 7, + "name": "revoked_by", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + true + ] + }, + "hash": "ddd3e85a88d9a782c54bdc33072747dd5db70cf76432e50635e22343092eadeb" +} diff --git a/.sqlx/query-eb7fe20b8124f1e9ba0f1ba74a4640cae40d6d1b1ddd503080cb75385246d7e1.json b/.sqlx/query-eb7fe20b8124f1e9ba0f1ba74a4640cae40d6d1b1ddd503080cb75385246d7e1.json new file mode 100644 index 0000000..ca39759 --- /dev/null +++ b/.sqlx/query-eb7fe20b8124f1e9ba0f1ba74a4640cae40d6d1b1ddd503080cb75385246d7e1.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE account_delegations\n SET revoked_at = NOW(), revoked_by = $1\n WHERE delegated_did = $2 AND controller_did = $3 AND revoked_at IS NULL\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "eb7fe20b8124f1e9ba0f1ba74a4640cae40d6d1b1ddd503080cb75385246d7e1" +} diff --git a/.sqlx/query-f18172e06c03978fb56a4e3acc9a926bdd0414f7883539113f7ec2d640ce184a.json b/.sqlx/query-f18172e06c03978fb56a4e3acc9a926bdd0414f7883539113f7ec2d640ce184a.json new file mode 100644 index 0000000..837aedc --- /dev/null +++ b/.sqlx/query-f18172e06c03978fb56a4e3acc9a926bdd0414f7883539113f7ec2d640ce184a.json @@ -0,0 +1,87 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n id,\n delegated_did,\n actor_did,\n controller_did,\n action_type as \"action_type: DelegationActionType\",\n action_details,\n ip_address,\n user_agent,\n created_at\n FROM delegation_audit_log\n WHERE delegated_did = $1\n ORDER BY created_at DESC\n LIMIT $2 OFFSET $3\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "delegated_did", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "actor_did", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "controller_did", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "action_type: DelegationActionType", + "type_info": { + "Custom": { + "name": "delegation_action_type", + "kind": { + "Enum": [ + "grant_created", + "grant_revoked", + "scopes_modified", + "token_issued", + "repo_write", + "blob_upload", + "account_action" + ] + } + } + } + }, + { + "ordinal": 5, + "name": "action_details", + "type_info": "Jsonb" + }, + { + "ordinal": 6, + "name": "ip_address", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "user_agent", + "type_info": "Text" + }, + { + "ordinal": 8, + "name": "created_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Text", + "Int8", + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + true, + false, + true, + true, + true, + false + ] + }, + "hash": "f18172e06c03978fb56a4e3acc9a926bdd0414f7883539113f7ec2d640ce184a" +} diff --git a/.sqlx/query-f3cd43a21db350887127cd7e0cd24e95a70571cc5e9b2278dda49a2538d794ae.json b/.sqlx/query-f3cd43a21db350887127cd7e0cd24e95a70571cc5e9b2278dda49a2538d794ae.json new file mode 100644 index 0000000..e7a3f6d --- /dev/null +++ b/.sqlx/query-f3cd43a21db350887127cd7e0cd24e95a70571cc5e9b2278dda49a2538d794ae.json @@ -0,0 +1,43 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO delegation_audit_log\n (delegated_did, actor_did, controller_did, action_type, action_details, ip_address, user_agent)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n RETURNING id\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Text", + { + "Custom": { + "name": "delegation_action_type", + "kind": { + "Enum": [ + "grant_created", + "grant_revoked", + "scopes_modified", + "token_issued", + "repo_write", + "blob_upload", + "account_action" + ] + } + } + }, + "Jsonb", + "Text", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "f3cd43a21db350887127cd7e0cd24e95a70571cc5e9b2278dda49a2538d794ae" +} diff --git a/.sqlx/query-f631186890dc38141299d8ecf6feda13c4ab8bd6e3834c64b2cd508305bed3aa.json b/.sqlx/query-f631186890dc38141299d8ecf6feda13c4ab8bd6e3834c64b2cd508305bed3aa.json new file mode 100644 index 0000000..f505579 --- /dev/null +++ b/.sqlx/query-f631186890dc38141299d8ecf6feda13c4ab8bd6e3834c64b2cd508305bed3aa.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM oauth_token WHERE did = $1 AND controller_did = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "f631186890dc38141299d8ecf6feda13c4ab8bd6e3834c64b2cd508305bed3aa" +} diff --git a/README.md b/README.md index a48f832..7451e70 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Another excellent PDS is [Cocoon](https://github.com/haileyok/cocoon), written i This software isn't an afterthought by a company with limited resources. -It is a superset of the reference PDS, including: passkeys and 2FA (WebAuthn/FIDO2, TOTP, backup codes, trusted devices), did:web support (PDS-hosted subdomains or bring-your-own), multi-channel communication (email, discord, telegram, signal) for verification and alerts, granular OAuth scopes with a consent UI showing human-readable descriptions, app passwords with granular permissions (read-only, post-only, or custom scopes), and a built-in web UI for account management, OAuth consent, repo browsing, and admin. +It is a superset of the reference PDS, including: passkeys and 2FA (WebAuthn/FIDO2, TOTP, backup codes, trusted devices), did:web support (PDS-hosted subdomains or bring-your-own), multi-channel communication (email, discord, telegram, signal) for verification and alerts, granular OAuth scopes with a consent UI showing human-readable descriptions, app passwords with granular permissions (read-only, post-only, or custom scopes), account delegation (letting others manage an account with configurable permission levels), and a built-in web UI for account management, OAuth consent, repo browsing, and admin. The PDS itself is a single small binary with no node/npm runtime. It does require postgres, valkey, and s3-compatible storage, which makes setup heavier than the reference PDS's sqlite. The tradeoff is that these are battle-tested pieces of infra that we already know how to scale, back up, and monitor. diff --git a/TODO.md b/TODO.md index d51837c..dd77b6f 100644 --- a/TODO.md +++ b/TODO.md @@ -2,23 +2,6 @@ ## Active development -### Delegated accounts -Accounts controlled by other accounts rather than having their own password. When logging in as a delegated account, OAuth asks you to authenticate with a linked controller account. Uses OAuth scopes as the permission model. - -- [ ] Account type flag in actors table (personal | delegated) -- [ ] account_delegations table (delegated_did, controller_did, granted_scopes[], granted_at, granted_by, revoked_at) -- [ ] Detect delegated account during authorize flow -- [ ] Redirect to "authenticate as controller" instead of password prompt -- [ ] Validate controller has delegation grant for this account -- [ ] Issue token with intersection of (requested scopes :intersection-emoji: granted scopes) -- [ ] Token includes act_as claim indicating delegation -- [ ] Define standard scope sets (owner, admin, editor, viewer) -- [ ] Create delegated account flow (no password, must add initial controller) -- [ ] Controller management page (add/remove controllers, modify scopes) -- [ ] "Act as" account switcher for users with delegation grants -- [ ] Log all actions with both actor DID and controller DID -- [ ] Audit log view for delegated account owners - ### Migration tool Seamless account migration built into the UI, inspired by pdsmoover. Users shouldn't need external tools or brain surgery on half-done account states. @@ -85,3 +68,5 @@ Auth: ES256K + HS256 dual support, JTI-only token storage, refresh token family Passkeys and 2FA: WebAuthn/FIDO2 passkey registration and authentication, TOTP with QR setup, backup codes (hashed, one-time use), passkey-only account creation, trusted devices (remember this browser), re-auth for sensitive actions, rate-limited 2FA attempts, settings UI for managing all auth methods. App password scopes: Granular permissions for app passwords using the same scope system as OAuth. Preset buttons for common use cases (full access, read-only, post-only), scope stored in session and preserved across token refresh, explicit RPC/repo/blob scope enforcement for restricted passwords. + +Account Delegation: Delegated accounts controlled by other accounts instead of passwords. OAuth delegation flow (authenticate as controller), scope-based permissions (owner/admin/editor/viewer presets), scope intersection (tokens limited to granted permissions), `act` claim for delegation tracking, creating delegated account flow, controller management UI, "act as" account switcher, comprehensive audit logging with actor/controller tracking, delegation-aware OAuth consent with permission limitation notices. diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index a083594..fca7ef4 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -25,9 +25,13 @@ import OAuth2FA from './routes/OAuth2FA.svelte' import OAuthTotp from './routes/OAuthTotp.svelte' import OAuthPasskey from './routes/OAuthPasskey.svelte' + import OAuthDelegation from './routes/OAuthDelegation.svelte' import OAuthError from './routes/OAuthError.svelte' import Security from './routes/Security.svelte' import TrustedDevices from './routes/TrustedDevices.svelte' + import Controllers from './routes/Controllers.svelte' + import DelegationAudit from './routes/DelegationAudit.svelte' + import ActAs from './routes/ActAs.svelte' import Home from './routes/Home.svelte' initI18n() @@ -95,12 +99,20 @@ return OAuthTotp case '/oauth/passkey': return OAuthPasskey + case '/oauth/delegation': + return OAuthDelegation case '/oauth/error': return OAuthError case '/security': return Security case '/trusted-devices': return TrustedDevices + case '/controllers': + return Controllers + case '/delegation-audit': + return DelegationAudit + case '/act-as': + return ActAs default: return Home } diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 3763cf5..71d7f59 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -94,6 +94,7 @@ export interface AppPassword { name: string; createdAt: string; scopes?: string; + createdByController?: string; } export interface InviteCode { diff --git a/frontend/src/lib/oauth.ts b/frontend/src/lib/oauth.ts index df40ec1..aaa61ac 100644 --- a/frontend/src/lib/oauth.ts +++ b/frontend/src/lib/oauth.ts @@ -44,20 +44,20 @@ function base64UrlEncode(buffer: ArrayBuffer): string { ); } -async function generateCodeChallenge(verifier: string): Promise { +export async function generateCodeChallenge(verifier: string): Promise { const hash = await sha256(verifier); return base64UrlEncode(hash); } -function generateState(): string { +export function generateState(): string { return generateRandomString(32); } -function generateCodeVerifier(): string { +export function generateCodeVerifier(): string { return generateRandomString(32); } -function saveOAuthState(state: OAuthState): void { +export function saveOAuthState(state: OAuthState): void { sessionStorage.setItem(OAUTH_STATE_KEY, state.state); sessionStorage.setItem(OAUTH_VERIFIER_KEY, state.codeVerifier); } diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index c057f30..329f990 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -6,6 +6,7 @@ "cancel": "Cancel", "back": "Back", "done": "Done", + "continue": "Continue", "refresh": "Refresh", "create": "Create", "delete": "Delete", @@ -271,7 +272,8 @@ "scopeFull": "Full Access", "scopeReadOnly": "Read Only", "scopePostOnly": "Post Only", - "scopeCustom": "Custom" + "scopeCustom": "Custom", + "byController": "By Controller" }, "sessions": { "title": "Active Sessions", @@ -853,6 +855,121 @@ "verifying": "Verifying...", "cancel": "Cancel" }, + "delegation": { + "title": "Account Delegation", + "loading": "Loading...", + "controllers": "Controllers", + "controllersDesc": "Accounts that can act on your behalf", + "noControllers": "No controllers have been granted access to your account.", + "inactive": "Inactive", + "did": "DID", + "granted": "Granted", + "remove": "Remove", + "removeConfirm": "Are you sure you want to remove this controller?", + "cannotAddControllers": "You cannot add controllers because this account controls other accounts. An account can either have controllers or control other accounts, but not both.", + "addController": "Add Controller", + "controllerDid": "Controller DID", + "accessLevel": "Access Level", + "adding": "Adding...", + "addControllerButton": "+ Add Controller", + "controllerAdded": "Controller added successfully", + "controllerRemoved": "Controller removed successfully", + "failedToAddController": "Failed to add controller", + "failedToRemoveController": "Failed to remove controller", + "controlledAccounts": "Controlled Accounts", + "controlledAccountsDesc": "Accounts you can act on behalf of", + "noControlledAccounts": "You do not have access to any delegated accounts.", + "actAs": "Act As", + "cannotControlAccounts": "You cannot control other accounts because this account has controllers. An account can either have controllers or control other accounts, but not both.", + "createDelegatedAccount": "Create Delegated Account", + "handle": "Handle", + "emailOptional": "Email (optional)", + "yourAccessLevel": "Your Access Level", + "creating": "Creating...", + "createAccount": "Create Account", + "createDelegatedAccountButton": "+ Create Delegated Account", + "accountCreated": "Created delegated account: {handle}", + "failedToCreateAccount": "Failed to create delegated account", + "auditLog": "Audit Log", + "auditLogDesc": "View all delegation activity", + "viewAuditLog": "View Audit Log", + "scopeOwner": "Owner", + "scopeViewer": "Viewer", + "scopeCustom": "Custom", + "backToControllers": "Back to Controllers", + "auditLogTitle": "Delegation Audit Log", + "noActivity": "No delegation activity recorded.", + "actor": "Actor", + "controller": "Controller", + "account": "Account", + "details": "Details", + "previous": "Previous", + "next": "Next", + "showing": "Showing {start} - {end} of {total}", + "refresh": "Refresh", + "failedToLoadAuditLog": "Failed to load audit log", + "actionGrantCreated": "Grant Created", + "actionGrantRevoked": "Grant Revoked", + "actionScopesModified": "Scopes Modified", + "actionTokenIssued": "Token Issued", + "actionRepoWrite": "Repository Write", + "actionBlobUpload": "Blob Upload", + "actionAccountAction": "Account Action" + }, + "actAs": { + "noAccountSpecified": "No account DID specified", + "failedToVerify": "Failed to verify delegation access", + "noAccess": "You do not have access to this account", + "failedToInitiate": "Failed to initiate OAuth flow", + "invalidResponse": "Invalid OAuth response", + "failedError": "Failed to initiate act-as: {error}", + "preparing": "Preparing to switch accounts...", + "title": "Act As", + "backToControllers": "Back to Controllers" + }, + "oauthDelegation": { + "loading": "Loading...", + "title": "Delegated Account", + "isDelegated": "{handle} is a delegated account.", + "enterControllerHandle": "Sign in with your controller account to access this account.", + "controllerHandle": "Controller handle", + "handlePlaceholder": "handle.example.com", + "checking": "Checking...", + "controllerNotFound": "Account not found or you don't have access to this delegated account", + "missingParams": "Missing delegation parameters", + "missingInfo": "Missing required information", + "passkeyCancelled": "Passkey authentication cancelled", + "passkeyFailed": "Passkey authentication failed", + "failedPasskeyStart": "Failed to start passkey login", + "authFailed": "Authentication failed", + "unexpectedResponse": "Unexpected response from server", + "signInAsController": "Sign In as Controller", + "authenticateAs": "Authenticate as {controller} to act on behalf of {delegated}", + "useDifferentController": "Use a different controller", + "signInWithPasskey": "Sign in with Passkey", + "authenticating": "Authenticating...", + "usePasskey": "Use Passkey", + "or": "or", + "password": "Password", + "enterPassword": "Enter password", + "rememberDevice": "Remember this device", + "signingIn": "Signing in...", + "signIn": "Sign In", + "goBack": "Go Back", + "unableToLoad": "Unable to load delegation info" + }, + "oauthConsent": { + "delegatedAccess": "Delegated Access", + "actingAs": "Acting as", + "controller": "Controller", + "accessLevel": "Access Level", + "readOnlyAccess": "Read-Only Access", + "readOnlyDesc": "View public information only. No write access to this account.", + "permissionsLimited": "Permissions Limited", + "permissionsLimitedDesc": "Your actual permissions will be limited to your {level} access level, regardless of what the app requests.", + "viewerLimitedDesc": "As a Viewer, you have read-only access. This app will not be able to create, update, or delete content on this account.", + "editorLimitedDesc": "As an Editor, you can create and edit content but cannot manage account settings or security." + }, "verifyChannel": { "title": "Verify Channel", "subtitle": "Enter the verification code sent to your notification channel.", diff --git a/frontend/src/locales/fi.json b/frontend/src/locales/fi.json index aae5281..081dec6 100644 --- a/frontend/src/locales/fi.json +++ b/frontend/src/locales/fi.json @@ -6,6 +6,7 @@ "cancel": "Peruuta", "back": "Takaisin", "done": "Valmis", + "continue": "Jatka", "refresh": "Päivitä", "create": "Luo", "delete": "Poista", @@ -271,7 +272,8 @@ "scopeFull": "Täydet oikeudet", "scopeReadOnly": "Vain luku", "scopePostOnly": "Vain julkaisut", - "scopeCustom": "Mukautettu" + "scopeCustom": "Mukautettu", + "byController": "Hallinnoijan luoma" }, "sessions": { "title": "Aktiiviset istunnot", @@ -888,5 +890,120 @@ "codeLabel": "Vahvistuskoodi", "codeHelp": "Kopioi koko koodi viestistäsi, mukaan lukien väliviivat.", "verifyButton": "Vahvista" + }, + "delegation": { + "title": "Tilin delegointi", + "loading": "Ladataan...", + "controllers": "Hallinnoijat", + "controllersDesc": "Tilit, jotka voivat toimia puolestasi", + "noControllers": "Tilillesi ei ole myönnetty hallinnoijia.", + "inactive": "Ei käytössä", + "did": "DID", + "granted": "Myönnetty", + "remove": "Poista", + "removeConfirm": "Haluatko varmasti poistaa tämän hallinnoijan?", + "cannotAddControllers": "Et voi lisätä hallinnoijia, koska tämä tili hallinnoi muita tilejä. Tili voi joko olla hallinnoija tai hallinnoidaan, mutta ei molempia.", + "addController": "Lisää hallinnoija", + "controllerDid": "Hallinnoijan DID", + "accessLevel": "Käyttöoikeustaso", + "adding": "Lisätään...", + "addControllerButton": "+ Lisää hallinnoija", + "controllerAdded": "Hallinnoija lisätty", + "controllerRemoved": "Hallinnoija poistettu", + "failedToAddController": "Hallinnoijan lisääminen epäonnistui", + "failedToRemoveController": "Hallinnoijan poistaminen epäonnistui", + "controlledAccounts": "Hallinnoidut tilit", + "controlledAccountsDesc": "Tilit, joiden puolesta voit toimia", + "noControlledAccounts": "Sinulla ei ole pääsyä delegoituihin tileihin.", + "actAs": "Toimi käyttäjänä", + "cannotControlAccounts": "Et voi hallinnoida muita tilejä, koska tällä tilillä on hallinnoijia. Tili voi joko olla hallinnoija tai hallinnoidaan, mutta ei molempia.", + "createDelegatedAccount": "Luo delegoitu tili", + "handle": "Käyttäjänimi", + "emailOptional": "Sähköposti (valinnainen)", + "yourAccessLevel": "Käyttöoikeustasosi", + "creating": "Luodaan...", + "createAccount": "Luo tili", + "createDelegatedAccountButton": "+ Luo delegoitu tili", + "accountCreated": "Delegoitu tili luotu: {handle}", + "failedToCreateAccount": "Delegoidun tilin luominen epäonnistui", + "auditLog": "Tapahtumaloki", + "auditLogDesc": "Näytä kaikki delegointitoiminta", + "viewAuditLog": "Näytä tapahtumaloki", + "scopeOwner": "Omistaja", + "scopeViewer": "Katsoja", + "scopeCustom": "Mukautettu", + "backToControllers": "Takaisin hallinnoijiin", + "auditLogTitle": "Delegoinnin tapahtumaloki", + "noActivity": "Delegointitoimintaa ei ole tallennettu.", + "actor": "Toimija", + "controller": "Hallinnoija", + "account": "Tili", + "details": "Tiedot", + "previous": "Edellinen", + "next": "Seuraava", + "showing": "Näytetään {start} - {end} / {total}", + "refresh": "Päivitä", + "failedToLoadAuditLog": "Tapahtumalokin lataaminen epäonnistui", + "actionGrantCreated": "Oikeus luotu", + "actionGrantRevoked": "Oikeus peruttu", + "actionScopesModified": "Oikeuksia muokattu", + "actionTokenIssued": "Token myönnetty", + "actionRepoWrite": "Tietovaraston kirjoitus", + "actionBlobUpload": "Tiedoston lataus", + "actionAccountAction": "Tilitoiminto" + }, + "actAs": { + "noAccountSpecified": "Tilin DID:tä ei määritetty", + "failedToVerify": "Delegointioikeuden tarkistus epäonnistui", + "noAccess": "Sinulla ei ole pääsyä tähän tiliin", + "failedToInitiate": "OAuth-kirjautumisen aloitus epäonnistui", + "invalidResponse": "Virheellinen OAuth-vastaus", + "failedError": "Toiminto epäonnistui: {error}", + "preparing": "Valmistellaan tilin vaihtoa...", + "title": "Toimi käyttäjänä", + "backToControllers": "Takaisin hallinnoijiin" + }, + "oauthDelegation": { + "loading": "Ladataan...", + "title": "Delegoitu tili", + "isDelegated": "{handle} on delegoitu tili.", + "enterControllerHandle": "Kirjaudu hallinnoijatililläsi päästäksesi tähän tiliin.", + "controllerHandle": "Hallinnoijan käyttäjätunnus", + "handlePlaceholder": "tunnus.esimerkki.fi", + "checking": "Tarkistetaan...", + "controllerNotFound": "Tiliä ei löytynyt tai sinulla ei ole pääsyä tähän delegoituun tiliin", + "missingParams": "Delegointiparametrit puuttuvat", + "missingInfo": "Vaaditut tiedot puuttuvat", + "passkeyCancelled": "Pääsyavaintunnistautuminen peruutettu", + "passkeyFailed": "Pääsyavaintunnistautuminen epäonnistui", + "failedPasskeyStart": "Pääsyavainkirjautumisen aloitus epäonnistui", + "authFailed": "Tunnistautuminen epäonnistui", + "unexpectedResponse": "Odottamaton vastaus palvelimelta", + "signInAsController": "Kirjaudu hallinnoijana", + "authenticateAs": "Tunnistaudu käyttäjänä {controller} toimiaksesi käyttäjän {delegated} puolesta", + "useDifferentController": "Käytä toista hallinnoijaa", + "signInWithPasskey": "Kirjaudu pääsyavaimella", + "authenticating": "Tunnistaudutaan...", + "usePasskey": "Käytä pääsyavainta", + "or": "tai", + "password": "Salasana", + "enterPassword": "Syötä salasana", + "rememberDevice": "Muista tämä laite", + "signingIn": "Kirjaudutaan...", + "signIn": "Kirjaudu", + "goBack": "Palaa takaisin", + "unableToLoad": "Delegointitietoja ei voitu ladata" + }, + "oauthConsent": { + "delegatedAccess": "Delegoitu pääsy", + "actingAs": "Toimii käyttäjänä", + "controller": "Hallinnoija", + "accessLevel": "Käyttöoikeustaso", + "readOnlyAccess": "Vain luku -oikeus", + "readOnlyDesc": "Näytä vain julkiset tiedot. Ei kirjoitusoikeutta tähän tiliin.", + "permissionsLimited": "Oikeudet rajoitettu", + "permissionsLimitedDesc": "Todelliset oikeutesi rajoitetaan {level}-käyttöoikeustasoosi riippumatta siitä, mitä sovellus pyytää.", + "viewerLimitedDesc": "Katselijana sinulla on vain lukuoikeus. Tämä sovellus ei voi luoda, muokata tai poistaa sisältöä tällä tilillä.", + "editorLimitedDesc": "Muokkaajana voit luoda ja muokata sisältöä, mutta et voi hallita tilin asetuksia tai tietoturvaa." } } diff --git a/frontend/src/locales/ja.json b/frontend/src/locales/ja.json index cffe171..5ec666e 100644 --- a/frontend/src/locales/ja.json +++ b/frontend/src/locales/ja.json @@ -6,6 +6,7 @@ "cancel": "キャンセル", "back": "戻る", "done": "完了", + "continue": "続行", "refresh": "更新", "create": "作成", "delete": "削除", @@ -271,7 +272,8 @@ "scopeFull": "フルアクセス", "scopeReadOnly": "読み取り専用", "scopePostOnly": "投稿のみ", - "scopeCustom": "カスタム" + "scopeCustom": "カスタム", + "byController": "管理者作成" }, "sessions": { "title": "アクティブセッション", @@ -888,5 +890,142 @@ "codeLabel": "認証コード", "codeHelp": "メッセージからハイフンを含む完全なコードをコピーしてください。", "verifyButton": "認証" + }, + "delegation": { + "title": "アカウント委任", + "controllers": "コントローラー", + "controllersDescription": "コントローラーはあなたのアカウントの管理者として行動できます。あなたが許可した操作を実行し、あなたの代わりに投稿を作成し、リポジトリを変更できます。", + "controlledAccounts": "管理アカウント", + "controlledAccountsDescription": "これらはあなたがコントローラーとして追加されているアカウントです。これらのアカウントで許可されたアクションを実行できます。", + "noControllers": "コントローラーはまだいません", + "noControlledAccounts": "管理アカウントはありません", + "addController": "コントローラーを追加", + "revokeAccess": "アクセスを取り消す", + "revokeConfirm": "このコントローラーのアクセスを取り消しますか?あなたのアカウントで操作できなくなります。", + "handle": "ハンドル", + "handlePlaceholder": "@user.bsky.social", + "did": "DID", + "didPlaceholder": "did:plc:...", + "scopes": "権限レベル", + "scopeOwner": "オーナー", + "scopeOwnerDesc": "完全な管理(すべてのアクションを実行可能)", + "scopeAdmin": "管理者", + "scopeAdminDesc": "投稿、アプリパスワード、設定の管理", + "scopeEditor": "編集者", + "scopeEditorDesc": "投稿、いいね、フォローの作成・管理", + "scopeViewer": "閲覧者", + "scopeViewerDesc": "リポジトリと設定の読み取り専用アクセス", + "scopeCustom": "カスタム", + "scopeCustomDesc": "個別の権限を選択", + "grantedAt": "許可日時", + "expiresAt": "有効期限", + "noExpiration": "無期限", + "actAs": "として行動", + "auditLog": "監査ログ", + "auditLogTitle": "委任監査ログ", + "backToControllers": "← コントローラーに戻る", + "loading": "読み込み中...", + "noActivity": "アクティビティはまだありません", + "actor": "アクター", + "controller": "コントローラー", + "account": "アカウント", + "details": "詳細", + "actionGrantCreated": "許可作成", + "actionGrantRevoked": "許可取り消し", + "actionScopesModified": "権限変更", + "actionTokenIssued": "トークン発行", + "actionRepoWrite": "リポジトリ書き込み", + "actionBlobUpload": "Blobアップロード", + "actionAccountAction": "アカウントアクション", + "previous": "前へ", + "next": "次へ", + "showing": "{start}~{end} / {total}件", + "refresh": "更新", + "failedToLoadAuditLog": "監査ログの読み込みに失敗しました", + "addControllerTitle": "コントローラーを追加", + "addControllerDescription": "このアカウントに対して指定した権限で操作できるユーザーを追加します。", + "controllerIdentifier": "コントローラーのハンドルまたはDID", + "selectScopes": "権限レベルを選択", + "add": "追加", + "adding": "追加中...", + "cancel": "キャンセル", + "accessLevel": "アクセスレベル", + "addControllerButton": "+ コントローラーを追加", + "auditLogDesc": "すべての委任アクティビティを表示", + "cannotAddControllers": "他のアカウントを管理しているため、コントローラーを追加できません。アカウントはコントローラーを持つか、他のアカウントを管理するかのいずれかのみ可能です。", + "cannotControlAccounts": "このアカウントにはコントローラーがいるため、他のアカウントを管理できません。アカウントはコントローラーを持つか、他のアカウントを管理するかのいずれかのみ可能です。", + "controlledAccountsDesc": "あなたが代わりに操作できるアカウント", + "controllerAdded": "コントローラーを追加しました", + "controllerDid": "コントローラーDID", + "controllerRemoved": "コントローラーを削除しました", + "controllersDesc": "あなたの代わりに操作できるアカウント", + "createAccount": "アカウントを作成", + "createDelegatedAccount": "委任アカウントを作成", + "createDelegatedAccountButton": "+ 委任アカウントを作成", + "creating": "作成中...", + "emailOptional": "メール(任意)", + "failedToAddController": "コントローラーの追加に失敗しました", + "failedToCreateAccount": "委任アカウントの作成に失敗しました", + "failedToRemoveController": "コントローラーの削除に失敗しました", + "granted": "許可日", + "inactive": "非アクティブ", + "remove": "削除", + "removeConfirm": "このコントローラーを削除しますか?", + "viewAuditLog": "監査ログを表示", + "yourAccessLevel": "あなたのアクセスレベル" + }, + "actAs": { + "title": "として行動", + "noAccountSpecified": "アカウントDIDが指定されていません", + "failedToVerify": "アカウントへのアクセスを確認できませんでした", + "noAccess": "このアカウントへのアクセス権がありません", + "failedToInitiate": "認証の開始に失敗しました", + "invalidResponse": "サーバーからの応答が無効です", + "failedError": "失敗しました: {error}", + "preparing": "委任アカウントへのログインを準備中...", + "backToControllers": "コントローラーに戻る" + }, + "oauthDelegation": { + "loading": "読み込み中...", + "title": "委任アカウント", + "isDelegated": "{handle} は委任アカウントです。", + "enterControllerHandle": "このアカウントにアクセスするには、コントローラーアカウントでサインインしてください。", + "controllerHandle": "コントローラーハンドル", + "handlePlaceholder": "handle.example.com", + "checking": "確認中...", + "controllerNotFound": "アカウントが見つからないか、この委任アカウントへのアクセス権がありません", + "missingParams": "委任パラメータが見つかりません", + "missingInfo": "必要な情報がありません", + "passkeyCancelled": "パスキー認証がキャンセルされました", + "passkeyFailed": "パスキー認証に失敗しました", + "failedPasskeyStart": "パスキーログインの開始に失敗しました", + "authFailed": "認証に失敗しました", + "unexpectedResponse": "サーバーから予期しない応答がありました", + "signInAsController": "コントローラーとしてサインイン", + "authenticateAs": "{controller} として認証して {delegated} の代わりに操作します", + "useDifferentController": "別のコントローラーを使用", + "signInWithPasskey": "パスキーでサインイン", + "authenticating": "認証中...", + "usePasskey": "パスキーを使用", + "or": "または", + "password": "パスワード", + "enterPassword": "パスワードを入力", + "rememberDevice": "このデバイスを記憶する", + "signingIn": "サインイン中...", + "signIn": "サインイン", + "goBack": "戻る", + "unableToLoad": "委任情報を読み込めませんでした" + }, + "oauthConsent": { + "delegatedAccess": "委任アクセス", + "actingAs": "次として行動中", + "controller": "コントローラー", + "accessLevel": "アクセスレベル", + "readOnlyAccess": "読み取り専用アクセス", + "readOnlyDesc": "公開情報のみ閲覧可能。このアカウントへの書き込みアクセスはありません。", + "permissionsLimited": "権限が制限されています", + "permissionsLimitedDesc": "アプリが何を要求しても、実際の権限は{level}アクセスレベルに制限されます。", + "viewerLimitedDesc": "閲覧者として、読み取り専用アクセスのみ可能です。このアプリはこのアカウントでコンテンツの作成、更新、削除ができません。", + "editorLimitedDesc": "編集者として、コンテンツの作成と編集が可能ですが、アカウント設定やセキュリティの管理はできません。" } } diff --git a/frontend/src/locales/ko.json b/frontend/src/locales/ko.json index a505f3a..59fce81 100644 --- a/frontend/src/locales/ko.json +++ b/frontend/src/locales/ko.json @@ -6,6 +6,7 @@ "cancel": "취소", "back": "뒤로", "done": "완료", + "continue": "계속", "refresh": "새로고침", "create": "생성", "delete": "삭제", @@ -271,7 +272,8 @@ "scopeFull": "전체 권한", "scopeReadOnly": "읽기 전용", "scopePostOnly": "게시만 가능", - "scopeCustom": "사용자 지정" + "scopeCustom": "사용자 지정", + "byController": "컨트롤러 생성" }, "sessions": { "title": "활성 세션", @@ -888,5 +890,142 @@ "codeLabel": "인증 코드", "codeHelp": "메시지에서 하이픈을 포함한 전체 코드를 복사하세요.", "verifyButton": "인증" + }, + "delegation": { + "title": "계정 위임", + "controllers": "컨트롤러", + "controllersDescription": "컨트롤러는 귀하의 계정 관리자로서 행동할 수 있습니다. 귀하가 허용한 작업을 수행하고, 귀하를 대신하여 게시물을 생성하고, 저장소를 수정할 수 있습니다.", + "controlledAccounts": "관리 계정", + "controlledAccountsDescription": "귀하가 컨트롤러로 추가된 계정들입니다. 이 계정들에서 허용된 작업을 수행할 수 있습니다.", + "noControllers": "아직 컨트롤러가 없습니다", + "noControlledAccounts": "관리 계정이 없습니다", + "addController": "컨트롤러 추가", + "revokeAccess": "액세스 취소", + "revokeConfirm": "이 컨트롤러의 액세스를 취소하시겠습니까? 귀하의 계정에서 더 이상 작업을 수행할 수 없습니다.", + "handle": "핸들", + "handlePlaceholder": "@user.bsky.social", + "did": "DID", + "didPlaceholder": "did:plc:...", + "scopes": "권한 수준", + "scopeOwner": "소유자", + "scopeOwnerDesc": "전체 관리(모든 작업 수행 가능)", + "scopeAdmin": "관리자", + "scopeAdminDesc": "게시물, 앱 비밀번호, 설정 관리", + "scopeEditor": "편집자", + "scopeEditorDesc": "게시물, 좋아요, 팔로우 생성 및 관리", + "scopeViewer": "뷰어", + "scopeViewerDesc": "저장소 및 설정 읽기 전용 액세스", + "scopeCustom": "사용자 정의", + "scopeCustomDesc": "개별 권한 선택", + "grantedAt": "허용 일시", + "expiresAt": "만료", + "noExpiration": "무기한", + "actAs": "로 활동", + "auditLog": "감사 로그", + "auditLogTitle": "위임 감사 로그", + "backToControllers": "← 컨트롤러로 돌아가기", + "loading": "로딩 중...", + "noActivity": "아직 활동이 없습니다", + "actor": "액터", + "controller": "컨트롤러", + "account": "계정", + "details": "세부정보", + "actionGrantCreated": "권한 생성", + "actionGrantRevoked": "권한 취소", + "actionScopesModified": "권한 수정", + "actionTokenIssued": "토큰 발급", + "actionRepoWrite": "저장소 쓰기", + "actionBlobUpload": "Blob 업로드", + "actionAccountAction": "계정 작업", + "previous": "이전", + "next": "다음", + "showing": "{start}~{end} / {total}개", + "refresh": "새로고침", + "failedToLoadAuditLog": "감사 로그를 불러오지 못했습니다", + "addControllerTitle": "컨트롤러 추가", + "addControllerDescription": "이 계정에서 지정된 권한으로 작업할 수 있는 사용자를 추가합니다.", + "controllerIdentifier": "컨트롤러 핸들 또는 DID", + "selectScopes": "권한 수준 선택", + "add": "추가", + "adding": "추가 중...", + "cancel": "취소", + "accessLevel": "액세스 수준", + "addControllerButton": "+ 컨트롤러 추가", + "auditLogDesc": "모든 위임 활동 보기", + "cannotAddControllers": "다른 계정을 관리하고 있어 컨트롤러를 추가할 수 없습니다. 계정은 컨트롤러를 가지거나 다른 계정을 관리할 수 있지만 둘 다는 불가능합니다.", + "cannotControlAccounts": "이 계정에 컨트롤러가 있어 다른 계정을 관리할 수 없습니다. 계정은 컨트롤러를 가지거나 다른 계정을 관리할 수 있지만 둘 다는 불가능합니다.", + "controlledAccountsDesc": "귀하가 대신 작업할 수 있는 계정", + "controllerAdded": "컨트롤러가 추가되었습니다", + "controllerDid": "컨트롤러 DID", + "controllerRemoved": "컨트롤러가 제거되었습니다", + "controllersDesc": "귀하를 대신하여 작업할 수 있는 계정", + "createAccount": "계정 생성", + "createDelegatedAccount": "위임 계정 생성", + "createDelegatedAccountButton": "+ 위임 계정 생성", + "creating": "생성 중...", + "emailOptional": "이메일 (선택사항)", + "failedToAddController": "컨트롤러 추가에 실패했습니다", + "failedToCreateAccount": "위임 계정 생성에 실패했습니다", + "failedToRemoveController": "컨트롤러 제거에 실패했습니다", + "granted": "허용일", + "inactive": "비활성", + "remove": "제거", + "removeConfirm": "이 컨트롤러를 제거하시겠습니까?", + "viewAuditLog": "감사 로그 보기", + "yourAccessLevel": "귀하의 액세스 수준" + }, + "actAs": { + "title": "로 활동", + "noAccountSpecified": "계정 DID가 지정되지 않았습니다", + "failedToVerify": "계정 액세스를 확인하지 못했습니다", + "noAccess": "이 계정에 대한 액세스 권한이 없습니다", + "failedToInitiate": "인증 시작에 실패했습니다", + "invalidResponse": "서버에서 잘못된 응답을 받았습니다", + "failedError": "실패: {error}", + "preparing": "위임 계정 로그인 준비 중...", + "backToControllers": "컨트롤러로 돌아가기" + }, + "oauthDelegation": { + "loading": "로딩 중...", + "title": "위임 계정", + "isDelegated": "{handle}은(는) 위임 계정입니다.", + "enterControllerHandle": "이 계정에 액세스하려면 컨트롤러 계정으로 로그인하세요.", + "controllerHandle": "컨트롤러 핸들", + "handlePlaceholder": "handle.example.com", + "checking": "확인 중...", + "controllerNotFound": "계정을 찾을 수 없거나 이 위임 계정에 대한 액세스 권한이 없습니다", + "missingParams": "위임 매개변수가 없습니다", + "missingInfo": "필요한 정보가 없습니다", + "passkeyCancelled": "패스키 인증이 취소되었습니다", + "passkeyFailed": "패스키 인증에 실패했습니다", + "failedPasskeyStart": "패스키 로그인 시작에 실패했습니다", + "authFailed": "인증에 실패했습니다", + "unexpectedResponse": "서버에서 예기치 않은 응답을 받았습니다", + "signInAsController": "컨트롤러로 로그인", + "authenticateAs": "{controller}(으)로 인증하여 {delegated}를 대신합니다", + "useDifferentController": "다른 컨트롤러 사용", + "signInWithPasskey": "패스키로 로그인", + "authenticating": "인증 중...", + "usePasskey": "패스키 사용", + "or": "또는", + "password": "비밀번호", + "enterPassword": "비밀번호 입력", + "rememberDevice": "이 기기 기억하기", + "signingIn": "로그인 중...", + "signIn": "로그인", + "goBack": "뒤로", + "unableToLoad": "위임 정보를 로드할 수 없습니다" + }, + "oauthConsent": { + "delegatedAccess": "위임 액세스", + "actingAs": "활동 계정", + "controller": "컨트롤러", + "accessLevel": "액세스 수준", + "readOnlyAccess": "읽기 전용 액세스", + "readOnlyDesc": "공개 정보만 볼 수 있습니다. 이 계정에 대한 쓰기 권한이 없습니다.", + "permissionsLimited": "권한 제한됨", + "permissionsLimitedDesc": "앱이 무엇을 요청하든 실제 권한은 {level} 액세스 수준으로 제한됩니다.", + "viewerLimitedDesc": "뷰어로서 읽기 전용 액세스 권한만 있습니다. 이 앱은 이 계정에서 콘텐츠를 생성, 수정 또는 삭제할 수 없습니다.", + "editorLimitedDesc": "편집자로서 콘텐츠를 생성하고 편집할 수 있지만 계정 설정이나 보안을 관리할 수 없습니다." } } diff --git a/frontend/src/locales/sv.json b/frontend/src/locales/sv.json index 8a69fac..4daaf9e 100644 --- a/frontend/src/locales/sv.json +++ b/frontend/src/locales/sv.json @@ -6,6 +6,7 @@ "cancel": "Avbryt", "back": "Tillbaka", "done": "Klar", + "continue": "Fortsätt", "refresh": "Uppdatera", "create": "Skapa", "delete": "Radera", @@ -271,7 +272,8 @@ "scopeFull": "Full åtkomst", "scopeReadOnly": "Endast läsning", "scopePostOnly": "Endast publicering", - "scopeCustom": "Anpassad" + "scopeCustom": "Anpassad", + "byController": "Av controller" }, "sessions": { "title": "Aktiva sessioner", @@ -888,5 +890,142 @@ "codeLabel": "Verifieringskod", "codeHelp": "Kopiera hela koden från ditt meddelande, inklusive bindestreck.", "verifyButton": "Verifiera" + }, + "delegation": { + "title": "Kontodelegering", + "controllers": "Kontrollanter", + "controllersDescription": "Kontrollanter kan agera som administratörer för ditt konto. De kan utföra åtgärder du tillåter, skapa inlägg för din räkning och modifiera din dataförvaring.", + "controlledAccounts": "Kontrollerade konton", + "controlledAccountsDescription": "Detta är konton där du har lagts till som kontrollant. Du kan utföra tillåtna åtgärder på dessa konton.", + "noControllers": "Inga kontrollanter ännu", + "noControlledAccounts": "Inga kontrollerade konton", + "addController": "Lägg till kontrollant", + "revokeAccess": "Återkalla åtkomst", + "revokeConfirm": "Återkalla denna kontrollants åtkomst? De kommer inte längre kunna utföra åtgärder på ditt konto.", + "handle": "Användarnamn", + "handlePlaceholder": "@user.bsky.social", + "did": "DID", + "didPlaceholder": "did:plc:...", + "scopes": "Behörighetsnivå", + "scopeOwner": "Ägare", + "scopeOwnerDesc": "Fullständig kontroll (kan utföra alla åtgärder)", + "scopeAdmin": "Administratör", + "scopeAdminDesc": "Hantera inlägg, applösenord, inställningar", + "scopeEditor": "Redaktör", + "scopeEditorDesc": "Skapa och hantera inlägg, gillningar, följningar", + "scopeViewer": "Läsare", + "scopeViewerDesc": "Endast läsåtkomst till dataförvaring och inställningar", + "scopeCustom": "Anpassad", + "scopeCustomDesc": "Välj individuella behörigheter", + "grantedAt": "Beviljad", + "expiresAt": "Upphör", + "noExpiration": "Ingen utgång", + "actAs": "Agera som", + "auditLog": "Granskningslogg", + "auditLogTitle": "Delegerings-granskningslogg", + "backToControllers": "← Tillbaka till kontrollanter", + "loading": "Laddar...", + "noActivity": "Ingen aktivitet ännu", + "actor": "Aktör", + "controller": "Kontrollant", + "account": "Konto", + "details": "Detaljer", + "actionGrantCreated": "Behörighet skapad", + "actionGrantRevoked": "Behörighet återkallad", + "actionScopesModified": "Behörigheter ändrade", + "actionTokenIssued": "Token utfärdad", + "actionRepoWrite": "Dataförvarsskrivning", + "actionBlobUpload": "Blob-uppladdning", + "actionAccountAction": "Kontoåtgärd", + "previous": "Föregående", + "next": "Nästa", + "showing": "{start}–{end} av {total}", + "refresh": "Uppdatera", + "failedToLoadAuditLog": "Kunde inte ladda granskningsloggen", + "addControllerTitle": "Lägg till kontrollant", + "addControllerDescription": "Lägg till en användare som kan utföra åtgärder på detta konto med specificerade behörigheter.", + "controllerIdentifier": "Kontrollantens användarnamn eller DID", + "selectScopes": "Välj behörighetsnivå", + "add": "Lägg till", + "adding": "Lägger till...", + "cancel": "Avbryt", + "accessLevel": "Åtkomstnivå", + "addControllerButton": "+ Lägg till kontrollant", + "auditLogDesc": "Visa all delegeringsaktivitet", + "cannotAddControllers": "Du kan inte lägga till kontrollanter eftersom detta konto kontrollerar andra konton. Ett konto kan antingen ha kontrollanter eller kontrollera andra konton, men inte båda.", + "cannotControlAccounts": "Du kan inte kontrollera andra konton eftersom detta konto har kontrollanter. Ett konto kan antingen ha kontrollanter eller kontrollera andra konton, men inte båda.", + "controlledAccountsDesc": "Konton du kan agera för", + "controllerAdded": "Kontrollant tillagd", + "controllerDid": "Kontrollant-DID", + "controllerRemoved": "Kontrollant borttagen", + "controllersDesc": "Konton som kan agera för dig", + "createAccount": "Skapa konto", + "createDelegatedAccount": "Skapa delegerat konto", + "createDelegatedAccountButton": "+ Skapa delegerat konto", + "creating": "Skapar...", + "emailOptional": "E-post (valfritt)", + "failedToAddController": "Kunde inte lägga till kontrollant", + "failedToCreateAccount": "Kunde inte skapa delegerat konto", + "failedToRemoveController": "Kunde inte ta bort kontrollant", + "granted": "Beviljad", + "inactive": "Inaktiv", + "remove": "Ta bort", + "removeConfirm": "Vill du ta bort denna kontrollant?", + "viewAuditLog": "Visa granskningslogg", + "yourAccessLevel": "Din åtkomstnivå" + }, + "actAs": { + "title": "Agera som", + "noAccountSpecified": "Inget konto-DID angivet", + "failedToVerify": "Kunde inte verifiera kontoåtkomst", + "noAccess": "Du har inte åtkomst till detta konto", + "failedToInitiate": "Kunde inte initiera autentisering", + "invalidResponse": "Ogiltigt svar från servern", + "failedError": "Misslyckades: {error}", + "preparing": "Förbereder inloggning till delegerat konto...", + "backToControllers": "Tillbaka till kontrollanter" + }, + "oauthDelegation": { + "loading": "Laddar...", + "title": "Delegerat konto", + "isDelegated": "{handle} är ett delegerat konto.", + "enterControllerHandle": "Logga in med ditt kontrollantkonto för att komma åt detta konto.", + "controllerHandle": "Kontrollantens användarnamn", + "handlePlaceholder": "handle.example.com", + "checking": "Kontrollerar...", + "controllerNotFound": "Kontot hittades inte eller så har du inte åtkomst till detta delegerade konto", + "missingParams": "Delegeringsparametrar saknas", + "missingInfo": "Nödvändig information saknas", + "passkeyCancelled": "Nyckelautentisering avbröts", + "passkeyFailed": "Nyckelautentisering misslyckades", + "failedPasskeyStart": "Kunde inte starta nyckelinloggning", + "authFailed": "Autentisering misslyckades", + "unexpectedResponse": "Oväntat svar från servern", + "signInAsController": "Logga in som kontrollant", + "authenticateAs": "Autentisera som {controller} för att agera på uppdrag av {delegated}", + "useDifferentController": "Använd en annan kontrollant", + "signInWithPasskey": "Logga in med nyckel", + "authenticating": "Autentiserar...", + "usePasskey": "Använd nyckel", + "or": "eller", + "password": "Lösenord", + "enterPassword": "Ange lösenord", + "rememberDevice": "Kom ihåg denna enhet", + "signingIn": "Loggar in...", + "signIn": "Logga in", + "goBack": "Gå tillbaka", + "unableToLoad": "Kunde inte ladda delegeringsinformation" + }, + "oauthConsent": { + "delegatedAccess": "Delegerad åtkomst", + "actingAs": "Agerar som", + "controller": "Kontrollant", + "accessLevel": "Åtkomstnivå", + "readOnlyAccess": "Endast läsåtkomst", + "readOnlyDesc": "Visa endast offentlig information. Ingen skrivåtkomst till detta konto.", + "permissionsLimited": "Behörigheter begränsade", + "permissionsLimitedDesc": "Dina faktiska behörigheter begränsas till din {level}-åtkomstnivå, oavsett vad appen begär.", + "viewerLimitedDesc": "Som visare har du endast läsåtkomst. Denna app kommer inte att kunna skapa, uppdatera eller ta bort innehåll på detta konto.", + "editorLimitedDesc": "Som redigerare kan du skapa och redigera innehåll men kan inte hantera kontoinställningar eller säkerhet." } } diff --git a/frontend/src/locales/zh.json b/frontend/src/locales/zh.json index f84e1f4..936f6c1 100644 --- a/frontend/src/locales/zh.json +++ b/frontend/src/locales/zh.json @@ -6,6 +6,7 @@ "cancel": "取消", "back": "返回", "done": "完成", + "continue": "继续", "refresh": "刷新", "create": "创建", "delete": "删除", @@ -271,7 +272,8 @@ "scopeFull": "完全访问", "scopeReadOnly": "只读", "scopePostOnly": "仅发帖", - "scopeCustom": "自定义" + "scopeCustom": "自定义", + "byController": "由控制者创建" }, "sessions": { "title": "登录会话", @@ -871,5 +873,142 @@ "codeLabel": "验证码", "codeHelp": "复制消息中的完整验证码,包括横线。", "verifyButton": "验证" + }, + "delegation": { + "title": "账户委托", + "controllers": "控制者", + "controllersDescription": "控制者可以作为您账户的管理员。他们可以执行您允许的操作,代表您发布帖子,以及修改您的数据仓库。", + "controlledAccounts": "受控账户", + "controlledAccountsDescription": "这些是您被添加为控制者的账户。您可以在这些账户上执行允许的操作。", + "noControllers": "暂无控制者", + "noControlledAccounts": "无受控账户", + "addController": "添加控制者", + "revokeAccess": "撤销访问", + "revokeConfirm": "撤销此控制者的访问权限?他们将无法再在您的账户上执行操作。", + "handle": "用户名", + "handlePlaceholder": "@user.bsky.social", + "did": "DID", + "didPlaceholder": "did:plc:...", + "scopes": "权限级别", + "scopeOwner": "所有者", + "scopeOwnerDesc": "完全控制(可执行所有操作)", + "scopeAdmin": "管理员", + "scopeAdminDesc": "管理帖子、应用专用密码、设置", + "scopeEditor": "编辑者", + "scopeEditorDesc": "创建和管理帖子、点赞、关注", + "scopeViewer": "查看者", + "scopeViewerDesc": "只读访问数据仓库和设置", + "scopeCustom": "自定义", + "scopeCustomDesc": "选择单独的权限", + "grantedAt": "授权时间", + "expiresAt": "过期时间", + "noExpiration": "永不过期", + "actAs": "代理操作", + "auditLog": "审计日志", + "auditLogTitle": "委托审计日志", + "backToControllers": "← 返回控制者", + "loading": "加载中...", + "noActivity": "暂无活动", + "actor": "执行者", + "controller": "控制者", + "account": "账户", + "details": "详情", + "actionGrantCreated": "授权创建", + "actionGrantRevoked": "授权撤销", + "actionScopesModified": "权限修改", + "actionTokenIssued": "令牌发放", + "actionRepoWrite": "仓库写入", + "actionBlobUpload": "Blob上传", + "actionAccountAction": "账户操作", + "previous": "上一页", + "next": "下一页", + "showing": "{start}–{end} / 共{total}条", + "refresh": "刷新", + "failedToLoadAuditLog": "加载审计日志失败", + "addControllerTitle": "添加控制者", + "addControllerDescription": "添加一个可以在此账户上执行指定权限操作的用户。", + "controllerIdentifier": "控制者用户名或 DID", + "selectScopes": "选择权限级别", + "add": "添加", + "adding": "添加中...", + "cancel": "取消", + "accessLevel": "访问级别", + "addControllerButton": "+ 添加控制者", + "auditLogDesc": "查看所有委托活动", + "cannotAddControllers": "因为此账户正在控制其他账户,所以无法添加控制者。账户只能拥有控制者或控制其他账户,不能同时两者兼备。", + "cannotControlAccounts": "因为此账户有控制者,所以无法控制其他账户。账户只能拥有控制者或控制其他账户,不能同时两者兼备。", + "controlledAccountsDesc": "您可以代理操作的账户", + "controllerAdded": "控制者已添加", + "controllerDid": "控制者 DID", + "controllerRemoved": "控制者已移除", + "controllersDesc": "可以代理操作您账户的账户", + "createAccount": "创建账户", + "createDelegatedAccount": "创建委托账户", + "createDelegatedAccountButton": "+ 创建委托账户", + "creating": "创建中...", + "emailOptional": "邮箱(可选)", + "failedToAddController": "添加控制者失败", + "failedToCreateAccount": "创建委托账户失败", + "failedToRemoveController": "移除控制者失败", + "granted": "授权日期", + "inactive": "未激活", + "remove": "移除", + "removeConfirm": "确定要移除此控制者吗?", + "viewAuditLog": "查看审计日志", + "yourAccessLevel": "您的访问级别" + }, + "actAs": { + "title": "代理操作", + "noAccountSpecified": "未指定账户 DID", + "failedToVerify": "无法验证账户访问权限", + "noAccess": "您没有此账户的访问权限", + "failedToInitiate": "无法启动认证", + "invalidResponse": "服务器返回无效响应", + "failedError": "失败: {error}", + "preparing": "正在准备登录委托账户...", + "backToControllers": "返回控制者" + }, + "oauthDelegation": { + "loading": "加载中...", + "title": "委托账户", + "isDelegated": "{handle} 是一个委托账户。", + "enterControllerHandle": "请使用您的控制者账户登录以访问此账户。", + "controllerHandle": "控制者用户名", + "handlePlaceholder": "handle.example.com", + "checking": "检查中...", + "controllerNotFound": "账户未找到或您没有权限访问此委托账户", + "missingParams": "缺少委托参数", + "missingInfo": "缺少必要信息", + "passkeyCancelled": "通行密钥认证已取消", + "passkeyFailed": "通行密钥认证失败", + "failedPasskeyStart": "无法启动通行密钥登录", + "authFailed": "认证失败", + "unexpectedResponse": "服务器返回意外响应", + "signInAsController": "以控制者身份登录", + "authenticateAs": "以 {controller} 身份认证以代表 {delegated} 操作", + "useDifferentController": "使用其他控制者", + "signInWithPasskey": "使用通行密钥登录", + "authenticating": "认证中...", + "usePasskey": "使用通行密钥", + "or": "或", + "password": "密码", + "enterPassword": "输入密码", + "rememberDevice": "记住此设备", + "signingIn": "登录中...", + "signIn": "登录", + "goBack": "返回", + "unableToLoad": "无法加载委托信息" + }, + "oauthConsent": { + "delegatedAccess": "委托访问", + "actingAs": "代理操作", + "controller": "控制者", + "accessLevel": "访问级别", + "readOnlyAccess": "只读访问", + "readOnlyDesc": "仅查看公开信息。无法对此账户进行写入操作。", + "permissionsLimited": "权限受限", + "permissionsLimitedDesc": "无论应用请求什么权限,您的实际权限将限制在{level}访问级别。", + "viewerLimitedDesc": "作为查看者,您只有只读权限。此应用无法在此账户上创建、更新或删除内容。", + "editorLimitedDesc": "作为编辑者,您可以创建和编辑内容,但无法管理账户设置或安全选项。" } } diff --git a/frontend/src/routes/ActAs.svelte b/frontend/src/routes/ActAs.svelte new file mode 100644 index 0000000..2f72af5 --- /dev/null +++ b/frontend/src/routes/ActAs.svelte @@ -0,0 +1,179 @@ + + +
+ {#if loading} +
+

{$_('actAs.preparing')}

+
+ {:else} +
+

{$_('actAs.title')}

+
+ + {#if error} +
{error}
+ {/if} + +
+ +
+ {/if} +
+ + diff --git a/frontend/src/routes/AppPasswords.svelte b/frontend/src/routes/AppPasswords.svelte index 798755f..6a1c448 100644 --- a/frontend/src/routes/AppPasswords.svelte +++ b/frontend/src/routes/AppPasswords.svelte @@ -173,6 +173,9 @@ {pw.name} {getScopeLabel(pw.scopes)} + {#if pw.createdByController} + {$_('appPasswords.byController')} + {/if} {$_('common.created')} {formatDate(pw.createdAt)} @@ -418,6 +421,16 @@ color: var(--success-text); } + .controller-badge { + font-size: var(--text-xs); + padding: var(--space-1) var(--space-2); + background: var(--info-bg, #e3f2fd); + border: 1px solid var(--info-border, #90caf9); + border-radius: var(--radius-sm); + color: var(--info-text, #1565c0); + cursor: help; + } + .date { font-size: var(--text-sm); color: var(--text-secondary); diff --git a/frontend/src/routes/Controllers.svelte b/frontend/src/routes/Controllers.svelte new file mode 100644 index 0000000..9ab4e39 --- /dev/null +++ b/frontend/src/routes/Controllers.svelte @@ -0,0 +1,680 @@ + + +
+
+ {$_('common.backToDashboard')} +

{$_('delegation.title')}

+
+ + {#if loading} +

{$_('delegation.loading')}

+ {:else} + {#if error} +
{error}
+ {/if} + + {#if success} +
{success}
+ {/if} + +
+
+

{$_('delegation.controllers')}

+

{$_('delegation.controllersDesc')}

+
+ + {#if controllers.length === 0} +

{$_('delegation.noControllers')}

+ {:else} +
+ {#each controllers as controller} +
+
+
+ @{controller.handle} + {getScopeLabel(controller.grantedScopes)} + {#if !controller.isActive} + {$_('delegation.inactive')} + {/if} +
+
+
+ {$_('delegation.did')} + {controller.did} +
+
+ {$_('delegation.granted')} + {formatDateTime(controller.grantedAt)} +
+
+
+
+ +
+
+ {/each} +
+ {/if} + + {#if !canAddControllers} +
+

{$_('delegation.cannotAddControllers')}

+
+ {:else if showAddController} +
+

{$_('delegation.addController')}

+
+ + +
+
+ + +
+
+ + +
+
+ {:else} + + {/if} +
+ +
+
+

{$_('delegation.controlledAccounts')}

+

{$_('delegation.controlledAccountsDesc')}

+
+ + {#if controlledAccounts.length === 0} +

{$_('delegation.noControlledAccounts')}

+ {:else} +
+ {#each controlledAccounts as account} +
+
+
+ @{account.handle} + {getScopeLabel(account.grantedScopes)} +
+
+
+ {$_('delegation.did')} + {account.did} +
+
+ {$_('delegation.granted')} + {formatDateTime(account.grantedAt)} +
+
+
+ +
+ {/each} +
+ {/if} + + {#if !canControlAccounts} +
+

{$_('delegation.cannotControlAccounts')}

+
+ {:else if showCreateDelegated} +
+

{$_('delegation.createDelegatedAccount')}

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ {:else} + + {/if} +
+ +
+
+

{$_('delegation.auditLog')}

+

{$_('delegation.auditLogDesc')}

+
+ {$_('delegation.viewAuditLog')} +
+ {/if} +
+ + diff --git a/frontend/src/routes/Dashboard.svelte b/frontend/src/routes/Dashboard.svelte index 6403d78..24c173d 100644 --- a/frontend/src/routes/Dashboard.svelte +++ b/frontend/src/routes/Dashboard.svelte @@ -186,6 +186,10 @@

{$_('dashboard.navRepo')}

{$_('dashboard.navRepoDesc')}

+ +

Delegation

+

Manage account controllers and delegated accounts

+
{#if auth.session.isAdmin}

{$_('dashboard.navAdmin')}

diff --git a/frontend/src/routes/DelegationAudit.svelte b/frontend/src/routes/DelegationAudit.svelte new file mode 100644 index 0000000..bf8f825 --- /dev/null +++ b/frontend/src/routes/DelegationAudit.svelte @@ -0,0 +1,322 @@ + + +
+
+ {$_('delegation.backToControllers')} +

{$_('delegation.auditLogTitle')}

+
+ + {#if loading} +

{$_('delegation.loading')}

+ {:else} + {#if error} +
{error}
+ {/if} + + {#if entries.length === 0} +

{$_('delegation.noActivity')}

+ {:else} +
+ {#each entries as entry} +
+
+ {formatActionType(entry.actionType)} + {formatDateTime(entry.createdAt)} +
+
+
+ {$_('delegation.actor')} + {truncateDid(entry.actorDid)} +
+ {#if entry.controllerDid} +
+ {$_('delegation.controller')} + {truncateDid(entry.controllerDid)} +
+ {/if} +
+ {$_('delegation.account')} + {truncateDid(entry.delegatedDid)} +
+ {#if entry.actionDetails} +
+ {$_('delegation.details')} + {formatActionDetails(entry.actionDetails)} +
+ {/if} +
+
+ {/each} +
+ + + {/if} + +
+ +
+ {/if} +
+ + diff --git a/frontend/src/routes/Home.svelte b/frontend/src/routes/Home.svelte index 68e1acf..7322898 100644 --- a/frontend/src/routes/Home.svelte +++ b/frontend/src/routes/Home.svelte @@ -178,6 +178,11 @@

App passwords with guardrails

Create app passwords that can only do specific things: read-only for feed readers, post-only for bots. Full control over what each password can access.

+ +
+

Delegate without sharing passwords

+

Let team members or tools manage your account with specific permission levels. They authenticate with their own credentials, you see everything they do in an audit log.

+

Everything in one place

diff --git a/frontend/src/routes/OAuthConsent.svelte b/frontend/src/routes/OAuthConsent.svelte index 49af5e7..53ec39d 100644 --- a/frontend/src/routes/OAuthConsent.svelte +++ b/frontend/src/routes/OAuthConsent.svelte @@ -20,6 +20,10 @@ scopes: ScopeInfo[] show_consent: boolean did: string + is_delegation?: boolean + controller_did?: string + controller_handle?: string + delegation_level?: string } let loading = $state(true) @@ -77,10 +81,14 @@ if (!consentData) return submitting = true - const approvedScopes = Object.entries(scopeSelections) + let approvedScopes = Object.entries(scopeSelections) .filter(([_, approved]) => approved) .map(([scope]) => scope) + if (approvedScopes.length === 0 && consentData.scopes.length === 0) { + approvedScopes = ['atproto'] + } + try { const response = await fetch('/oauth/authorize/consent', { method: 'POST', @@ -183,36 +191,82 @@

{$_('oauth.consent.permissionsRequested')}

- {#each Object.entries(scopeGroups) as [category, scopes]} -
-

{category}

- {#each scopes as scope} - - {/each} + {#if consentData.scopes.length === 0} +
+
+
+ {$_('oauthConsent.readOnlyAccess')} + {$_('oauthConsent.readOnlyDesc')} +
+
- {/each} + {:else} + {#each Object.entries(scopeGroups) as [category, scopes]} +
+

{category}

+ {#each scopes as scope} + + {/each} +
+ {/each} + {/if}