mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
feat: instance usernames (#3382)
* feat: instance usernames * fix: sqlx + ee ref * feat: make one-off button for enabling * chore: set ee ref * fix: permissions and nits * fix: admin instance username + sqlx build * fix: revert stupid change * fix: only create instance username when setting enabled * chore: update to ee latest
This commit is contained in:
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue SET permissioned_as = ('u/' || $1) WHERE permissioned_as = ('u/' || $2) AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "036af7b1cf6d731647fd718458944b9a9759bdb034e73f3065cde6a2f88c8dce"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE usr SET username = $1 WHERE email = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "03cb31686d34035f2dc64d4dff93265af0e44a7e2dabdf28fd9c9327b5b53118"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE app SET policy = jsonb_set(policy, ARRAY['on_behalf_of'], to_jsonb('u/' || $1)) WHERE policy->>'on_behalf_of' = ('u/' || $2) AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "03d4ec7a5feda9001e98c2e55f0788399ee41c9b128099937abb483f1d89a2c6"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE completed_job SET created_by = $1 WHERE created_by = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0721acae4f627df4687bb43b830a47faeee5c0a152cda8d62794c14dd200fac1"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE input SET created_by = $1 WHERE created_by = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "07ce3bce5f71b13d8aaf51f7d8aef8e32ff9e3e9cf58daab1c02276a7f0bd163"
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO password (email, login_type, verified) VALUES ($1, 'saml', true) ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "120fa12a4c2fdecf1da58ffba0c0aff06ea62f94ff357c446a8e2311e083efdf"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE completed_job SET script_path = REGEXP_REPLACE(script_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE script_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1c2254c15696d3dbc091488311676641ba3c6f1f1b5e006fc75427c9b231d323"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE resource SET extra_perms = extra_perms - ('u/' || $2) || jsonb_build_object(('u/' || $1), extra_perms->('u/' || $2)) WHERE extra_perms ? ('u/' || $2) AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1f003a8a6a9402f5f7021b032281a9aa70a8f89777e95e0415877ffa90ecd87c"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE token SET scopes = array(select regexp_replace(unnest(scopes), 'run:([^/]+)/u/' || $2 || '/(.+)', 'run:\\1/u/' || $1 || '/\\2')) WHERE EXISTS (SELECT 1 FROM UNNEST(scopes) scope WHERE scope LIKE ('run:%/u/' || $2 || '/%')) AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1f9e5d943e597ad9ed4e77088666b89d70db73de0b92f390a976fe3ce88dc4c5"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE draft SET value = to_json(jsonb_set(to_jsonb(value), ARRAY['path'], to_jsonb(REGEXP_REPLACE(value->>'path','u/' || $2 || '/(.*)','u/' || $1 || '/\\1')))) WHERE value->>'path' LIKE ('u/' || $2 || '/%') AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "23e100a803d89cdce24486adc6de0904a68a011157f50ec57e39afb582e48d64"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE usr_to_group SET usr = $1 WHERE usr = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "2f0de5417088df098a200a67197155e10e909bd02c91082341dd73bb1d6d62e1"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE token SET owner = ('u/' || $1) WHERE owner = ('u/' || $2) AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "32ab08ef55f665b1944001ca6ad661940d59e211f60437437b5d53a6ee6a79a4"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE schedule SET script_path = REGEXP_REPLACE(script_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE script_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "35e0f5488724e9bb7595748754bbdd7c5c8d643bbbfa9b39f5b3d5d4684eab3a"
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM usr WHERE username = $1 and email != $2 UNION SELECT 1 FROM password WHERE username = $1 UNION SELECT 1 FROM pending_user WHERE username = $1)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "3baafb31f22d4de32bd11185f96939c0e5def7bbee4a7f25ca208a667d0dc612"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE app SET extra_perms = extra_perms - ('u/' || $2) || jsonb_build_object(('u/' || $1), extra_perms->('u/' || $2)) WHERE extra_perms ? ('u/' || $2) AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4a585c1f92628b8763930764befdf38ce2ea7df71b013bd960796cc8230797e6"
|
||||
}
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO password(email, verified, password_hash, login_type, super_admin, name, company)\n VALUES ($1, $2, $3, 'password', $4, $5, $6)",
|
||||
"query": "INSERT INTO password(email, verified, password_hash, login_type, super_admin, name, company, username)\n VALUES ($1, $2, $3, 'password', $4, $5, $6, $7)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -10,10 +10,11 @@
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "11b1586acdfc180c5a077861ee1f7201fcbcec9d0ebada464f9d952c9c3e400d"
|
||||
"hash": "54c6be1469a80d88fd6d5c30772bb6698610189bac63c2caee3f4d52b0851f31"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE favorite SET usr = $1 WHERE usr = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "59f6de54d604c7f724ae886928b2f8bf71a5c80991207d2576fb965e5ac60f15"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE completed_job SET permissioned_as = ('u/' || $1) WHERE permissioned_as = ('u/' || $2) AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5d79c4817696d0ba0d2062eef27ff3856fc4a732adf5796e9b06c826406584dc"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue SET schedule_path = REGEXP_REPLACE(schedule_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE schedule_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6239e15d2389e24e290d86bb96e3ade1656cd403000a34a19a942993b60ff612"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE variable SET extra_perms = extra_perms - ('u/' || $2) || jsonb_build_object(('u/' || $1), extra_perms->('u/' || $2)) WHERE extra_perms ? ('u/' || $2) AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "675ca010b31b3504403b56c4a8e523b6748cf8b289a8c058688bb7b3a143deaf"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue SET script_path = REGEXP_REPLACE(script_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE script_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6818cae88492f83baf55f54bf5dd5397e04dbe771445818f88903ee5677b3631"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE flow SET extra_perms = extra_perms - ('u/' || $2) || jsonb_build_object(('u/' || $1), extra_perms->('u/' || $2)) WHERE extra_perms ? ('u/' || $2) AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "70bad6ac45db6d03c59aa375f915ec55d1c07998b1bd0268725d34f7f0b98103"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE flow SET edited_by = $1 WHERE edited_by = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "729bf764b10dc821594bbffbc157c061ec6eb09b3cc22c672e0e17da455ac7ef"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE app SET path = REGEXP_REPLACE(path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE path LIKE ('u/' || $2 || '/%') AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "74bee966d2b5e4c1f9b1739bfd6ed828571aa2395d602d06f6ac755c1001c7c9"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE capture SET created_by = $1 WHERE created_by = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "793870919e0b61b12bcbb30439201acf664cbdc3d8a45fda7ab12b5898b819f1"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE script SET path = REGEXP_REPLACE(path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE path LIKE ('u/' || $2 || '/%') AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "7e950917ef1d80026cae863f0744dbbe229e41b083d2a56940d8032d5dfe4189"
|
||||
}
|
||||
+8
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT email, login_type::TEXT, super_admin, verified, name, company FROM password WHERE email = $1",
|
||||
"query": "SELECT email, login_type::TEXT, super_admin, verified, name, company, username FROM password WHERE email = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -32,6 +32,11 @@
|
||||
"ordinal": 5,
|
||||
"name": "company",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "username",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -45,8 +50,9 @@
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "90719f6230467b08e5f2cc89271bcf5e4a6cec39e9d9b42ef3b90f09b3135b83"
|
||||
"hash": "8813665f8adfcab0daefbac2cc6b50e427dfd1c12d12895451affd307dc59c37"
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT workspace_id, username FROM usr WHERE email = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "username",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "8a8f8c4a9ac35f44e5640ba38b9ef523d890b1687dc40a5b98367285f904e137"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE schedule SET edited_by = $1 WHERE edited_by = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "979d80eed9500fc0b76c5707ef07db43de1e6d5dee4a7de2fa99fad3292f0d4c"
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM pending_user WHERE email = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "9969f99d81b2fa581a1da8f7cc3722f60cbd41814f8a17006c82f48888f05bb6"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE schedule SET path = REGEXP_REPLACE(path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE path LIKE ('u/' || $2 || '/%') AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "9a3f8ca29c86fe31f8cc339897b3b49c1d3aa7b8a95be8a9101bd232ecef843a"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE flow SET path = REGEXP_REPLACE(path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE path LIKE ('u/' || $2 || '/%') AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a459d973c6392af6af1af8b17bc735128fa06ee80e867e2421e70df7a722081a"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE folder SET owners = ARRAY_REPLACE(owners, 'u/' || $2, 'u/' || $1) WHERE ('u/' || $2) = ANY(owners) AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a474d655786f0b70e659da8c88e4f934c7d27234491e850b4406f4d5c8da3293"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO password (email, login_type, verified, username) VALUES ($1, 'saml', true, $2) ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a59aac0bc593d99aedd14fd9606f11191590a12ea98a671e172b867306959884"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE resource SET path = REGEXP_REPLACE(path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE path LIKE ('u/' || $2 || '/%') AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a65cb39ac640a86c90d1648d1a9e93fa5a7c1be385454153f15625bd58c3cc3a"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS (SELECT 1 FROM password WHERE email = $1)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "a670b88ea83d69997f1b70873b927bfe0790d5e1c347e5b15835c8996c039cd1"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue SET created_by = $1 WHERE created_by = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a6feede7d9c3060b1e4c94bb4fa700d38a0d1b9d25429021c08fc94d57f952d2"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE variable SET path = REGEXP_REPLACE(path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE path LIKE ('u/' || $2 || '/%') AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "abb962124b848ae07e517994ca9ad10e28ed47c5c9cdecf449a3758ae65cb62f"
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT p.email as \"email!\", u.username as \"username?\" FROM password p LEFT JOIN usr u ON p.email = u.email WHERE p.username IS NULL AND (SELECT COUNT(DISTINCT username) FROM usr WHERE email = p.email) <= 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "email!",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "username?",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE draft SET path = REGEXP_REPLACE(path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE path LIKE ('u/' || $2 || '/%') AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b5fbceb4fde1a4c00d8d36be6f2fb612b0aec2baa6ed76c09bd11ae462642221"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE password SET username = $1 WHERE email = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b705a3692c98081501bff467daf347e6cb6f6eff578882ce7e5e87db05b4a1e0"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue SET canceled_by = $1 WHERE canceled_by = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "bf282f7ed77ed09bb44b5e253725a15acefac87bedb2725827938c54a3c0e8f8"
|
||||
}
|
||||
+8
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT email, login_type::text, verified, super_admin, name, company from password ORDER BY super_admin DESC, email LIMIT $1 OFFSET $2",
|
||||
"query": "SELECT email, login_type::text, verified, super_admin, name, company, username from password ORDER BY super_admin DESC, email LIMIT $1 OFFSET $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -32,6 +32,11 @@
|
||||
"ordinal": 5,
|
||||
"name": "company",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "username",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -46,8 +51,9 @@
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "5f2cbdfe6eaf6cfaee1ec830678a5fdcf1024c8fab2b4a40d93dd5ffa4e7ba5c"
|
||||
"hash": "c2060e8cacef6c3b5ce51ed203a2dbafc18d66f2924d1fe518c6728997647db2"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT username FROM password WHERE email = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "username",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "c58c35f74739ae139848099ef379aa721d37f97d3aab19d5ee798a650b757af9"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE folder SET extra_perms = extra_perms - ('u/' || $2) || jsonb_build_object(('u/' || $1), extra_perms->('u/' || $2)) WHERE extra_perms ? ('u/' || $2) AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "cc9e2460b8fded240eff64cf3cf348f9bd36a86233f3b0a3e73f51698ed353dd"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE script SET created_by = $1 WHERE created_by = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d48f06a5ddf41fc11e31c6f53cfb9b9e18256877087a1601bd6f51f826575f26"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE schedule SET extra_perms = extra_perms - ('u/' || $2) || jsonb_build_object(('u/' || $1), extra_perms->('u/' || $2)) WHERE extra_perms ? ('u/' || $2) AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d6b7d48c4ebf1161f2b781829b57957bbe7aaaf734c63c2a20c2ba600b423b49"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT username FROM pending_user WHERE email = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "username",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "d6c0f1fe3a6dd0b9ccb9d4765a73dc3e7cf71f1ed15d155009aa61657040f4f6"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE script SET extra_perms = extra_perms - ('u/' || $2) || jsonb_build_object(('u/' || $1), extra_perms->('u/' || $2)) WHERE extra_perms ? ('u/' || $2) AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e38dd0f093636efa2ec1cda4618fc104f7b8ea88130692ce9c1390bda289627a"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO usr (workspace_id, username, email, is_admin, operator) VALUES ($1, $2, $3, false, $4)",
|
||||
"query": "INSERT INTO usr (workspace_id, username, email, is_admin, operator) VALUES ($1, $2, $3, false, $4) ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -13,5 +13,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a92f6de42016f24f9b8b4c0d997dc557e796c004ff318aede92481df044c004a"
|
||||
"hash": "e822d186203fe809b764007ad7c02870a3b7d93ae43b40ac2cd3181dffab0837"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE completed_job SET canceled_by = $1 WHERE canceled_by = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e9d9edc030061c5ccf2fb4294acbbae160e97faede6934d90f4882d806c14813"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO pending_user (email, username) VALUES ($1, $2)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f2d85395311fd9b3293fc4d23644ab486d6f0bf633c2cc38ace9c1f1cd12ea80"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE app_version SET created_by = $1 WHERE created_by = $2 AND EXISTS (SELECT 1 FROM app WHERE workspace_id = $3 AND app.id = app_version.app_id)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f362c8bb2e214f218115a9c2808d01427d43581ad11cc492f59363aaea730788"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE raw_app SET extra_perms = extra_perms - ('u/' || $2) || jsonb_build_object(('u/' || $1), extra_perms->('u/' || $2)) WHERE extra_perms ? ('u/' || $2) AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f7be09062288e645544e3ca3912a67ab6418985b299b118fbde24e29a9bc1871"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE group_ SET extra_perms = extra_perms - ('u/' || $2) || jsonb_build_object(('u/' || $1), extra_perms->('u/' || $2)) WHERE extra_perms ? ('u/' || $2) AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "fad42991fb062a34d7b5952ec8dd169e4e6ae16ab34ccce0950805122c27a2b4"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE completed_job SET schedule_path = REGEXP_REPLACE(schedule_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE schedule_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "fe5e941310ffdbbe7cc5f3e2beb213999ff64bbe4f8e2532a7aa48184cb008e7"
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
11edcb35cb919903b6d6766502c06559788e467d
|
||||
e632d5f821ee229823288b30b7ef5d3e59db07fa
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE password ADD COLUMN username VARCHAR(50)
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,6 @@
|
||||
-- Add up migration script here
|
||||
create table pending_user (
|
||||
email varchar(255) not null primary key,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
username varchar(50) not null
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,5 @@
|
||||
INSERT INTO
|
||||
global_settings (name, value)
|
||||
SELECT 'automate_username_creation', 'true'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM workspace WHERE id != 'admins')
|
||||
ON CONFLICT DO NOTHING;
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
UPDATE password SET username = 'admin' WHERE email = 'admin@windmill.dev';
|
||||
@@ -340,6 +340,76 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/users/username_info/{email}:
|
||||
get:
|
||||
summary: global username info (require super admin)
|
||||
operationId: globalUsernameInfo
|
||||
tags:
|
||||
- user
|
||||
parameters:
|
||||
- name: email
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: user renamed
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
username:
|
||||
type: string
|
||||
workspace_usernames:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
workspace_id:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
required:
|
||||
- workspace_id
|
||||
- username
|
||||
required:
|
||||
- username
|
||||
- workspace_usernames
|
||||
|
||||
/users/rename/{email}:
|
||||
post:
|
||||
summary: global rename user (require super admin)
|
||||
operationId: globalUserRename
|
||||
tags:
|
||||
- user
|
||||
parameters:
|
||||
- name: email
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
description: new username
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
new_username:
|
||||
type: string
|
||||
required:
|
||||
- new_username
|
||||
responses:
|
||||
"200":
|
||||
description: user renamed
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/users/delete/{email}:
|
||||
delete:
|
||||
summary: global delete user (require super admin)
|
||||
@@ -880,7 +950,6 @@ paths:
|
||||
type: string
|
||||
required:
|
||||
- workspace_id
|
||||
- username
|
||||
responses:
|
||||
"200":
|
||||
description: status
|
||||
@@ -977,7 +1046,6 @@ paths:
|
||||
- email
|
||||
- is_admin
|
||||
- operator
|
||||
- username
|
||||
responses:
|
||||
"200":
|
||||
description: status
|
||||
@@ -1750,7 +1818,6 @@ paths:
|
||||
items:
|
||||
$ref: "#/components/schemas/UserUsage"
|
||||
|
||||
|
||||
/w/{workspace}/users/list_usernames:
|
||||
get:
|
||||
summary: list usernames
|
||||
@@ -4840,7 +4907,6 @@ paths:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
|
||||
/w/{workspace}/jobs/workflow_as_code/{job_id}/{entrypoint}:
|
||||
post:
|
||||
summary: run code-workflow task
|
||||
@@ -6509,7 +6575,7 @@ paths:
|
||||
type: string
|
||||
required:
|
||||
- owner
|
||||
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: owner added to folder
|
||||
@@ -8924,7 +8990,6 @@ components:
|
||||
name:
|
||||
type: string
|
||||
|
||||
|
||||
CreateResource:
|
||||
type: object
|
||||
properties:
|
||||
@@ -9325,7 +9390,6 @@ components:
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- username
|
||||
|
||||
Workspace:
|
||||
type: object
|
||||
@@ -9376,6 +9440,8 @@ components:
|
||||
type: string
|
||||
company:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
|
||||
required:
|
||||
- email
|
||||
|
||||
@@ -11,7 +11,7 @@ use std::time::Duration;
|
||||
use crate::{
|
||||
db::{ApiAuthed, DB},
|
||||
ee::validate_license_key,
|
||||
utils::require_super_admin,
|
||||
utils::{generate_instance_username_for_all_users, require_super_admin},
|
||||
HTTP_CLIENT,
|
||||
};
|
||||
|
||||
@@ -26,7 +26,7 @@ use serde::Deserialize;
|
||||
use tokio::time::timeout;
|
||||
use windmill_common::{
|
||||
error::{self, to_anyhow, JsonResult, Result},
|
||||
global_settings::ENV_SETTINGS,
|
||||
global_settings::{AUTOMATE_USERNAME_CREATION_SETTING, ENV_SETTINGS},
|
||||
server::Smtp,
|
||||
};
|
||||
|
||||
@@ -144,6 +144,22 @@ pub async fn set_global_setting_internal(
|
||||
key: String,
|
||||
value: serde_json::Value,
|
||||
) -> error::Result<()> {
|
||||
match key.as_str() {
|
||||
AUTOMATE_USERNAME_CREATION_SETTING => {
|
||||
if value.clone().as_bool().unwrap_or(false) {
|
||||
generate_instance_username_for_all_users(db)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error::Error::InternalErr(format!(
|
||||
"Failed to generate instance wide usernames: {}",
|
||||
err
|
||||
))
|
||||
})?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match value {
|
||||
serde_json::Value::Null => {
|
||||
delete_global_setting(db, &key).await?;
|
||||
@@ -162,6 +178,7 @@ pub async fn set_global_setting_internal(
|
||||
tracing::info!("Set global setting {} to {}", key, v);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -170,7 +187,10 @@ pub async fn get_global_setting(
|
||||
authed: ApiAuthed,
|
||||
Path(key): Path<String>,
|
||||
) -> JsonResult<serde_json::Value> {
|
||||
if !key.starts_with("default_error_handler_") && !key.starts_with("default_recovery_handler_") {
|
||||
if !key.starts_with("default_error_handler_")
|
||||
&& !key.starts_with("default_recovery_handler_")
|
||||
&& key != AUTOMATE_USERNAME_CREATION_SETTING
|
||||
{
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
}
|
||||
let value = sqlx::query!("SELECT value FROM global_settings WHERE name = $1", key)
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use regex::Regex;
|
||||
use sqlx::{Postgres, Transaction};
|
||||
use windmill_common::{
|
||||
error::{self, Error},
|
||||
users::{SUPERADMIN_NOTIFICATION_EMAIL, SUPERADMIN_SECRET_EMAIL},
|
||||
@@ -30,3 +32,144 @@ pub async fn require_super_admin(db: &DB, email: &str) -> error::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref INVALID_USERNAME_CHARS: Regex = Regex::new(r"[^A-Za-z0-9_]").unwrap();
|
||||
}
|
||||
|
||||
pub async fn generate_instance_wide_unique_username<'c>(
|
||||
tx: &mut Transaction<'c, Postgres>,
|
||||
email: &str,
|
||||
) -> error::Result<String> {
|
||||
let mut username = email.split('@').next().unwrap().to_string();
|
||||
|
||||
username = INVALID_USERNAME_CHARS
|
||||
.replace_all(&mut username, "")
|
||||
.to_string();
|
||||
|
||||
if username.is_empty() {
|
||||
username = "user".to_string()
|
||||
}
|
||||
|
||||
let base_username = username.clone();
|
||||
let mut username_conflict = true;
|
||||
let mut i = 1;
|
||||
while username_conflict {
|
||||
if i > 1000 {
|
||||
return Err(Error::InternalErr(format!(
|
||||
"too many username conflicts for {}",
|
||||
email
|
||||
)));
|
||||
}
|
||||
if i > 1 {
|
||||
username = format!("{}{}", base_username, i)
|
||||
}
|
||||
username_conflict = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM usr WHERE username = $1 and email != $2 UNION SELECT 1 FROM password WHERE username = $1 UNION SELECT 1 FROM pending_user WHERE username = $1)",
|
||||
&username,
|
||||
&email
|
||||
)
|
||||
.fetch_one(&mut **tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
Ok(username)
|
||||
}
|
||||
|
||||
pub async fn generate_instance_username_for_all_users(db: &DB) -> error::Result<()> {
|
||||
let mut tx = db.begin().await?;
|
||||
// get users that have a no instance username and either 1 or 0 workspace usernames
|
||||
let users = sqlx::query!(r#"SELECT p.email as "email!", u.username as "username?" FROM password p LEFT JOIN usr u ON p.email = u.email WHERE p.username IS NULL AND (SELECT COUNT(DISTINCT username) FROM usr WHERE email = p.email) <= 1"#)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
|
||||
for user in users {
|
||||
let username = if let Some(username) = user.username {
|
||||
// if has workspace username, check that username is unique
|
||||
let username_conflict = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM usr WHERE username = $1 and email != $2 UNION SELECT 1 FROM password WHERE username = $1 UNION SELECT 1 FROM pending_user WHERE username = $1)",
|
||||
&username,
|
||||
&user.email
|
||||
).fetch_one(&mut *tx).await?.unwrap_or(false);
|
||||
|
||||
if !username_conflict {
|
||||
username
|
||||
} else {
|
||||
generate_instance_wide_unique_username(&mut tx, &user.email).await?
|
||||
}
|
||||
} else {
|
||||
generate_instance_wide_unique_username(&mut tx, &user.email).await?
|
||||
};
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE password SET username = $1 WHERE email = $2",
|
||||
&username,
|
||||
&user.email
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_instance_username_or_create_pending<'c>(
|
||||
tx: &mut Transaction<'c, Postgres>,
|
||||
email: &str,
|
||||
) -> error::Result<String> {
|
||||
let user = sqlx::query_scalar!("SELECT username FROM password WHERE email = $1", email)
|
||||
.fetch_optional(&mut **tx)
|
||||
.await?;
|
||||
|
||||
if let Some(opt_username) = user {
|
||||
if let Some(username) = opt_username {
|
||||
Ok(username)
|
||||
} else {
|
||||
Err(Error::BadRequest(format!("No instance-wide username found for {email}. The user has different usernames for different workspaces. Ask the instance administrator to solve the conflict in the instance settings.")))
|
||||
}
|
||||
} else {
|
||||
let pending_username =
|
||||
sqlx::query_scalar!("SELECT username FROM pending_user WHERE email = $1", email)
|
||||
.fetch_optional(&mut **tx)
|
||||
.await?;
|
||||
|
||||
if let Some(username) = pending_username {
|
||||
Ok(username)
|
||||
} else {
|
||||
let username = generate_instance_wide_unique_username(&mut *tx, email).await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO pending_user (email, username) VALUES ($1, $2)",
|
||||
email,
|
||||
username
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("creating pending user: {e}")))?;
|
||||
|
||||
Ok(username)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_and_delete_pending_username_or_generate<'c>(
|
||||
tx: &mut Transaction<'c, Postgres>,
|
||||
email: &str,
|
||||
) -> error::Result<String> {
|
||||
let username = sqlx::query_scalar!("SELECT username FROM pending_user WHERE email = $1", email)
|
||||
.fetch_optional(&mut **tx)
|
||||
.await?;
|
||||
|
||||
if let Some(username) = username {
|
||||
sqlx::query!("DELETE FROM pending_user WHERE email = $1", email)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
Ok(username)
|
||||
} else {
|
||||
let username = generate_instance_wide_unique_username(&mut *tx, email).await?;
|
||||
Ok(username)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::db::ApiAuthed;
|
||||
use crate::utils::{get_instance_username_or_create_pending, INVALID_USERNAME_CHARS};
|
||||
use crate::BASE_URL;
|
||||
use crate::{
|
||||
apps::AppWithLastVersion,
|
||||
@@ -46,6 +47,7 @@ use windmill_common::workspaces::WorkspaceGitSyncSettings;
|
||||
use windmill_common::{
|
||||
error::{to_anyhow, Error, JsonResult, Result},
|
||||
flows::Flow,
|
||||
global_settings::AUTOMATE_USERNAME_CREATION_SETTING,
|
||||
oauth2::WORKSPACE_SLACK_BOT_TOKEN_PATH,
|
||||
scripts::{Schema, Script, ScriptLang},
|
||||
utils::{paginate, rd_string, require_admin, Pagination},
|
||||
@@ -229,7 +231,7 @@ struct EditLargeFileStorageConfig {
|
||||
struct CreateWorkspace {
|
||||
id: String,
|
||||
name: String,
|
||||
username: String,
|
||||
username: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -272,7 +274,7 @@ pub struct NewWorkspaceInvite {
|
||||
#[derive(Deserialize)]
|
||||
pub struct NewWorkspaceUser {
|
||||
pub email: String,
|
||||
pub username: String,
|
||||
pub username: Option<String>,
|
||||
pub is_admin: bool,
|
||||
pub operator: bool,
|
||||
}
|
||||
@@ -538,47 +540,62 @@ async fn auto_add_user(
|
||||
operator: &bool,
|
||||
tx: &mut Transaction<'_, Postgres>,
|
||||
) -> Result<()> {
|
||||
let mut username = email
|
||||
.split('@')
|
||||
.next()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.replace(".", "");
|
||||
let automate_username_creation = sqlx::query_scalar!(
|
||||
"SELECT value FROM global_settings WHERE name = $1",
|
||||
AUTOMATE_USERNAME_CREATION_SETTING,
|
||||
)
|
||||
.fetch_optional(&mut **tx)
|
||||
.await?
|
||||
.map(|v| v.as_bool())
|
||||
.flatten()
|
||||
.unwrap_or(false);
|
||||
|
||||
username = INVALID_USERNAME_CHARS
|
||||
.replace_all(&mut username, "")
|
||||
.to_string();
|
||||
let username = if automate_username_creation {
|
||||
get_instance_username_or_create_pending(&mut *tx, &email).await?
|
||||
} else {
|
||||
let mut username = email
|
||||
.split('@')
|
||||
.next()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.replace(".", "");
|
||||
|
||||
if username.is_empty() {
|
||||
username = "user".to_string()
|
||||
}
|
||||
username = INVALID_USERNAME_CHARS
|
||||
.replace_all(&mut username, "")
|
||||
.to_string();
|
||||
|
||||
let base_username = username.clone();
|
||||
let mut username_conflict = true;
|
||||
let mut i = 1;
|
||||
while username_conflict {
|
||||
if i > 1000 {
|
||||
return Err(Error::InternalErr(format!(
|
||||
"too many username conflicts for {}",
|
||||
email
|
||||
)));
|
||||
if username.is_empty() {
|
||||
username = "user".to_string()
|
||||
}
|
||||
if i > 1 {
|
||||
username = format!("{}{}", base_username, i)
|
||||
|
||||
let base_username = username.clone();
|
||||
let mut username_conflict = true;
|
||||
let mut i = 1;
|
||||
while username_conflict {
|
||||
if i > 1000 {
|
||||
return Err(Error::InternalErr(format!(
|
||||
"too many username conflicts for {}",
|
||||
email
|
||||
)));
|
||||
}
|
||||
if i > 1 {
|
||||
username = format!("{}{}", base_username, i)
|
||||
}
|
||||
username_conflict = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM usr WHERE username = $1 AND workspace_id = $2)",
|
||||
&username,
|
||||
&w_id
|
||||
)
|
||||
.fetch_one(&mut **tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
i += 1;
|
||||
}
|
||||
username_conflict = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM usr WHERE username = $1 AND workspace_id = $2)",
|
||||
&username,
|
||||
&w_id
|
||||
)
|
||||
.fetch_one(&mut **tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
i += 1;
|
||||
}
|
||||
username
|
||||
};
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO usr (workspace_id, username, email, is_admin, operator) VALUES ($1, $2, $3, false, $4)",
|
||||
"INSERT INTO usr (workspace_id, username, email, is_admin, operator) VALUES ($1, $2, $3, false, $4) ON CONFLICT DO NOTHING",
|
||||
&w_id,
|
||||
&username,
|
||||
&email,
|
||||
@@ -1249,8 +1266,6 @@ lazy_static::lazy_static! {
|
||||
}
|
||||
};
|
||||
|
||||
pub static ref INVALID_USERNAME_CHARS: Regex = Regex::new(r"[^A-Za-z0-9_]").unwrap();
|
||||
|
||||
}
|
||||
|
||||
async fn create_workspace_require_superadmin() -> String {
|
||||
@@ -1330,13 +1345,35 @@ async fn create_workspace(
|
||||
// .execute(&mut tx)
|
||||
// .await?;
|
||||
|
||||
let automate_username_creation = sqlx::query_scalar!(
|
||||
"SELECT value FROM global_settings WHERE name = $1",
|
||||
AUTOMATE_USERNAME_CREATION_SETTING,
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?
|
||||
.map(|v| v.as_bool())
|
||||
.flatten()
|
||||
.unwrap_or(false);
|
||||
|
||||
let username = if automate_username_creation {
|
||||
if nw.username.is_some() && nw.username.unwrap().len() > 0 {
|
||||
return Err(Error::BadRequest(
|
||||
"username is not allowed when username creation is automated".to_string(),
|
||||
));
|
||||
}
|
||||
get_instance_username_or_create_pending(&mut tx, &authed.email).await?
|
||||
} else {
|
||||
nw.username
|
||||
.ok_or(Error::BadRequest("username is required".to_string()))?
|
||||
};
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO usr
|
||||
(workspace_id, email, username, is_admin)
|
||||
VALUES ($1, $2, $3, true)",
|
||||
nw.id,
|
||||
authed.email,
|
||||
nw.username,
|
||||
username,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
@@ -1353,7 +1390,7 @@ async fn create_workspace(
|
||||
"INSERT INTO usr_to_group
|
||||
VALUES ($1, 'all', $2)",
|
||||
nw.id,
|
||||
nw.username
|
||||
username
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
@@ -1741,16 +1778,11 @@ async fn add_user(
|
||||
nu.email = nu.email.to_lowercase();
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
if !VALID_USERNAME.is_match(&nu.username) {
|
||||
return Err(windmill_common::error::Error::BadRequest(format!(
|
||||
"Usermame can only contain alphanumeric characters and underscores and must start with a letter"
|
||||
)));
|
||||
}
|
||||
|
||||
let already_exists_email = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM usr WHERE workspace_id = $1 AND email = $2)",
|
||||
&w_id,
|
||||
username,
|
||||
nu.email,
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?
|
||||
@@ -1763,13 +1795,44 @@ async fn add_user(
|
||||
)));
|
||||
}
|
||||
|
||||
let automate_username_creation = sqlx::query_scalar!(
|
||||
"SELECT value FROM global_settings WHERE name = $1",
|
||||
AUTOMATE_USERNAME_CREATION_SETTING,
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?
|
||||
.map(|v| v.as_bool())
|
||||
.flatten()
|
||||
.unwrap_or(false);
|
||||
|
||||
let username = if automate_username_creation {
|
||||
if nu.username.is_some() && nu.username.unwrap().len() > 0 {
|
||||
return Err(Error::BadRequest(
|
||||
"username is not allowed when username creation is automated".to_string(),
|
||||
));
|
||||
}
|
||||
get_instance_username_or_create_pending(&mut tx, &nu.email).await?
|
||||
} else {
|
||||
let username = nu
|
||||
.username
|
||||
.ok_or(Error::BadRequest("username is required".to_string()))?;
|
||||
|
||||
if !VALID_USERNAME.is_match(&username) {
|
||||
return Err(windmill_common::error::Error::BadRequest(format!(
|
||||
"Usermame can only contain alphanumeric characters and underscores and must start with a letter"
|
||||
)));
|
||||
}
|
||||
|
||||
username
|
||||
};
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO usr
|
||||
(workspace_id, email, username, is_admin, operator)
|
||||
VALUES ($1, $2, $3, $4, $5)",
|
||||
&w_id,
|
||||
nu.email,
|
||||
nu.username,
|
||||
username,
|
||||
nu.is_admin,
|
||||
nu.operator
|
||||
)
|
||||
@@ -1780,7 +1843,7 @@ async fn add_user(
|
||||
Group,
|
||||
"INSERT INTO usr_to_group (workspace_id, usr, group_) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING",
|
||||
&w_id,
|
||||
nu.username,
|
||||
username,
|
||||
"all",
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
@@ -1788,7 +1851,7 @@ async fn add_user(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&nu.username,
|
||||
&username,
|
||||
"users.add_to_workspace",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
|
||||
@@ -22,6 +22,8 @@ pub const EXPOSE_DEBUG_METRICS_SETTING: &str = "expose_debug_metrics";
|
||||
pub const KEEP_JOB_DIR_SETTING: &str = "keep_job_dir";
|
||||
pub const REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING: &str = "require_preexisting_user_for_oauth";
|
||||
|
||||
pub const AUTOMATE_USERNAME_CREATION_SETTING: &str = "automate_username_creation";
|
||||
|
||||
pub const ENV_SETTINGS: [&str; 55] = [
|
||||
"DISABLE_NSJAIL",
|
||||
"MODE",
|
||||
|
||||
+7
-2
@@ -12,6 +12,7 @@ import {
|
||||
Table,
|
||||
UserService,
|
||||
WorkspaceService,
|
||||
SettingService,
|
||||
} from "./deps.ts";
|
||||
import { requireLogin } from "./context.ts";
|
||||
|
||||
@@ -240,11 +241,15 @@ export async function add(
|
||||
`Workspace at id ${workspaceId} on ${remote} does not exist. Creating...`
|
||||
)
|
||||
);
|
||||
const automateUsernameCreation: boolean =
|
||||
(await SettingService.getGlobal({
|
||||
key: "automate_username_creation",
|
||||
})) ?? false;
|
||||
await WorkspaceService.createWorkspace({
|
||||
requestBody: {
|
||||
id: workspaceId,
|
||||
name: opts.createWorkspaceName ?? workspaceName,
|
||||
username: opts.createUsername,
|
||||
username: automateUsernameCreation ? undefined : opts.createUsername,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -360,7 +365,7 @@ const command = new Command()
|
||||
)
|
||||
.option(
|
||||
"--create-username <username:string>",
|
||||
"Specify your own username in the newly created workspace. Ignored if --create is not specified or the workspace already exists.",
|
||||
"Specify your own username in the newly created workspace. Ignored if --create is not specified, the workspace already exists or automatic username creation is enabled on the instance.",
|
||||
{
|
||||
default: "admin",
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { globalEmailInvite, superadmin, workspaceStore } from '$lib/stores'
|
||||
import { UserService, WorkspaceService } from '$lib/gen'
|
||||
import { SettingService, UserService, WorkspaceService } from '$lib/gen'
|
||||
import { Button, Popup } from './common'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { isCloudHosted } from '$lib/cloud'
|
||||
@@ -23,12 +23,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
let automateUsernameCreation = false
|
||||
async function getAutomateUsernameCreationSetting() {
|
||||
automateUsernameCreation =
|
||||
(await SettingService.getGlobal({ key: 'automate_username_creation' })) ?? false
|
||||
}
|
||||
getAutomateUsernameCreationSetting()
|
||||
|
||||
async function addUser() {
|
||||
await WorkspaceService.addUser({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
email,
|
||||
username,
|
||||
username: automateUsernameCreation ? undefined : username,
|
||||
is_admin: selected == 'admin',
|
||||
operator: selected == 'operator'
|
||||
}
|
||||
@@ -37,6 +44,7 @@
|
||||
if (!(await UserService.existsEmail({ email }))) {
|
||||
let isSuperadmin = $superadmin
|
||||
if (!isCloudHosted()) {
|
||||
const emailCopy = email
|
||||
sendUserToast(
|
||||
`User ${email} is not registered yet on the instance. ${
|
||||
!isSuperadmin
|
||||
@@ -49,7 +57,7 @@
|
||||
{
|
||||
label: 'Add user to the instance',
|
||||
callback: () => {
|
||||
$globalEmailInvite = email
|
||||
$globalEmailInvite = emailCopy
|
||||
goto('#superadmin-settings')
|
||||
}
|
||||
}
|
||||
@@ -79,8 +87,10 @@
|
||||
<span class="text-xs mb-1 leading-6">Email</span>
|
||||
<input type="email mb-1" on:keyup={handleKeyUp} placeholder="email" bind:value={email} />
|
||||
|
||||
<span class="text-xs mb-1 pt-2 leading-6">Username</span>
|
||||
<input type="text" on:keyup={handleKeyUp} placeholder="username" bind:value={username} />
|
||||
{#if !automateUsernameCreation}
|
||||
<span class="text-xs mb-1 pt-2 leading-6">Username</span>
|
||||
<input type="text" on:keyup={handleKeyUp} placeholder="username" bind:value={username} />
|
||||
{/if}
|
||||
|
||||
<span class="text-xs mb-1 pt-2 leading-6">Role</span>
|
||||
<ToggleButtonGroup bind:selected class="mb-4">
|
||||
@@ -117,7 +127,7 @@
|
||||
username = undefined
|
||||
})
|
||||
}}
|
||||
disabled={email === undefined || username === undefined}
|
||||
disabled={email === undefined || (!automateUsernameCreation && username === undefined)}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
<script lang="ts">
|
||||
import { SettingService, UserService } from '$lib/gen'
|
||||
import { Button, Popup } from './common'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import Alert from './common/alert/Alert.svelte'
|
||||
import { autoPlacement } from '@floating-ui/core'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let email: string
|
||||
export let username: string
|
||||
export let isConflict = false
|
||||
|
||||
let usernameInfo:
|
||||
| {
|
||||
username: string
|
||||
workspace_usernames: {
|
||||
workspace_id: string
|
||||
username: string
|
||||
}[]
|
||||
}
|
||||
| undefined = undefined
|
||||
|
||||
function handleKeyUp(event: KeyboardEvent) {
|
||||
const key = event.key
|
||||
if (key === 'Enter') {
|
||||
event.preventDefault()
|
||||
renameUser()
|
||||
}
|
||||
}
|
||||
|
||||
async function getUsernameInfo() {
|
||||
usernameInfo = await UserService.globalUsernameInfo({
|
||||
email
|
||||
})
|
||||
if (isConflict) {
|
||||
username = usernameInfo.username
|
||||
}
|
||||
}
|
||||
|
||||
getUsernameInfo()
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function renameUser() {
|
||||
const automateUsernameCreation =
|
||||
(await SettingService.getGlobal({ key: 'automate_username_creation' })) ?? false
|
||||
|
||||
if (!automateUsernameCreation) {
|
||||
sendUserToast(
|
||||
'Modifying the username is only possible when the creation of usernames is automated and defined at instance level..'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
await UserService.globalUserRename({
|
||||
email,
|
||||
requestBody: {
|
||||
new_username: username
|
||||
}
|
||||
})
|
||||
|
||||
sendUserToast(`Rename user ${email} to ${username}`)
|
||||
|
||||
dispatch('renamed')
|
||||
}
|
||||
</script>
|
||||
|
||||
<Popup
|
||||
floatingConfig={{
|
||||
middleware: [
|
||||
autoPlacement({
|
||||
allowedPlacements: ['bottom-end', 'top-end']
|
||||
})
|
||||
]
|
||||
}}
|
||||
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
|
||||
let:close
|
||||
>
|
||||
<svelte:fragment slot="button">
|
||||
<Button color={isConflict ? 'red' : 'light'} size="xs" spacingSize="xs2" nonCaptureEvent={true}
|
||||
>{isConflict ? 'Fix username conflict' : 'Change username'}</Button
|
||||
>
|
||||
</svelte:fragment>
|
||||
<div class="flex flex-col max-w-2xl p-2">
|
||||
<span class="text-sm mb-2 leading-6 font-semibold"
|
||||
>{isConflict ? 'Fix username conflict' : 'Change username'}</span
|
||||
>
|
||||
|
||||
<span class="text-xs mb-1 leading-6"
|
||||
>{isConflict ? 'Auto-generated instance username' : 'New username'}</span
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="mb-4"
|
||||
on:keyup={handleKeyUp}
|
||||
bind:value={username}
|
||||
disabled={isConflict}
|
||||
/>
|
||||
|
||||
{#if isConflict}
|
||||
<Alert title="Username conflict" class="mb-4">
|
||||
Users are required to have an instance-wide username that is shared across all workspaces.
|
||||
However, this user has different usernames in different workspaces.
|
||||
|
||||
{#if usernameInfo?.workspace_usernames && usernameInfo.workspace_usernames.filter((w) => w.username !== username).length > 0}
|
||||
<br />
|
||||
<br />
|
||||
Workspaces requiring username modification: {usernameInfo.workspace_usernames
|
||||
.filter((w) => w.username !== username)
|
||||
.map((wu) => `${wu.workspace_id} (${wu.username})`)
|
||||
.join(', ')}
|
||||
{/if}
|
||||
</Alert>
|
||||
{/if}
|
||||
|
||||
{#if !isConflict && usernameInfo?.workspace_usernames && usernameInfo.workspace_usernames.filter((w) => w.username !== username).length > 0}
|
||||
<Alert title="Concerned workspaces" class="mb-4">
|
||||
{usernameInfo.workspace_usernames
|
||||
.filter((w) => w.username !== username)
|
||||
.map((wu) => `${wu.workspace_id}`)
|
||||
.join(', ')}
|
||||
</Alert>
|
||||
{/if}
|
||||
|
||||
<Alert type="warning" title="Manual action required" class="mb-4">
|
||||
This operation does not handle references in scripts, workflows and applications to scripts in
|
||||
the workspace, and references in resources to variables. You will have to handle those
|
||||
manually.
|
||||
<br />
|
||||
</Alert>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
color="blue"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
renameUser().then(() => {
|
||||
close(null)
|
||||
})
|
||||
}}
|
||||
disabled={email === undefined || !username}
|
||||
>
|
||||
Confirm username change
|
||||
</Button>
|
||||
</div>
|
||||
</Popup>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { UserService, GlobalUserInfo } from '$lib/gen'
|
||||
import { UserService, GlobalUserInfo, SettingService } from '$lib/gen'
|
||||
import TableCustom from '$lib/components/TableCustom.svelte'
|
||||
import InviteGlobalUser from '$lib/components/InviteGlobalUser.svelte'
|
||||
import { Button, Drawer, DrawerContent, Tab, Tabs } from '$lib/components/common'
|
||||
@@ -18,7 +18,9 @@
|
||||
import { ExternalLink } from 'lucide-svelte'
|
||||
import { settingsKeys } from './instanceSettings'
|
||||
import ConfirmationModal from './common/confirmationModal/ConfirmationModal.svelte'
|
||||
|
||||
import ChangeInstanceUsername from './ChangeInstanceUsername.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import { isCloudHosted } from '$lib/cloud'
|
||||
let drawer: Drawer
|
||||
let filter = ''
|
||||
|
||||
@@ -52,6 +54,23 @@
|
||||
let nbDisplayed = 50
|
||||
|
||||
let instanceSettings
|
||||
|
||||
let automateUsernameCreation = false
|
||||
async function getAutomateUsernameCreationSetting() {
|
||||
automateUsernameCreation =
|
||||
(await SettingService.getGlobal({ key: 'automate_username_creation' })) ?? false
|
||||
}
|
||||
getAutomateUsernameCreationSetting()
|
||||
let automateUsernameModalOpen = false
|
||||
async function enableAutomateUsernameCreationSetting() {
|
||||
await SettingService.setGlobal({
|
||||
key: 'automate_username_creation',
|
||||
requestBody: { value: true }
|
||||
})
|
||||
getAutomateUsernameCreationSetting()
|
||||
sendUserToast('Automatic username creation enabled')
|
||||
listUsers()
|
||||
}
|
||||
</script>
|
||||
|
||||
<SearchItems
|
||||
@@ -93,6 +112,42 @@
|
||||
<div class="pt-4" />
|
||||
<TabContent value="users">
|
||||
<div class="h-full">
|
||||
{#if !automateUsernameCreation && !isCloudHosted()}
|
||||
<div class="mb-4">
|
||||
<h3 class="mb-2">
|
||||
Automatic username creation
|
||||
<Tooltip>
|
||||
Automatically create a username for new users based on their email, shared
|
||||
across workspaces.
|
||||
</Tooltip>
|
||||
</h3>
|
||||
<Button
|
||||
btnClasses="w-auto"
|
||||
size="sm"
|
||||
color="dark"
|
||||
on:click={() => {
|
||||
automateUsernameModalOpen = true
|
||||
}}
|
||||
>
|
||||
Enable (recommended)
|
||||
</Button>
|
||||
<ConfirmationModal
|
||||
open={automateUsernameModalOpen}
|
||||
on:confirmed={() => {
|
||||
automateUsernameModalOpen = false
|
||||
enableAutomateUsernameCreationSetting()
|
||||
}}
|
||||
on:canceled={() => (automateUsernameModalOpen = false)}
|
||||
title="Automatic username creation"
|
||||
confirmationText="Enable"
|
||||
>
|
||||
Once activated, it will not be possible to disable this feature. In case
|
||||
existing users have different usernames in different workspaces, you will have
|
||||
to manually confirm the username for each user.
|
||||
</ConfirmationModal>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="py-2 mb-4">
|
||||
<InviteGlobalUser on:new={listUsers} />
|
||||
</div>
|
||||
@@ -108,16 +163,38 @@
|
||||
<th>email</th>
|
||||
<th>auth</th>
|
||||
<th>name</th>
|
||||
{#if automateUsernameCreation}
|
||||
<th>username</th>
|
||||
{/if}
|
||||
<th />
|
||||
<th />
|
||||
</tr>
|
||||
<tbody slot="body" class="overflow-y-auto w-full h-full max-h-full">
|
||||
{#if filteredUsers && users}
|
||||
{#each filteredUsers.slice(0, nbDisplayed) as { email, super_admin, login_type, name } (email)}
|
||||
{#each filteredUsers.slice(0, nbDisplayed) as { email, super_admin, login_type, name, username } (email)}
|
||||
<tr class="border">
|
||||
<td>{email}</td>
|
||||
<td>{login_type}</td>
|
||||
<td><span class="break-words">{truncate(name ?? '', 30)}</span></td>
|
||||
|
||||
{#if automateUsernameCreation}
|
||||
<td>
|
||||
{#if username}
|
||||
{username}
|
||||
{:else}
|
||||
{#key filteredUsers.map((u) => u.username).join()}
|
||||
<ChangeInstanceUsername
|
||||
username=""
|
||||
{email}
|
||||
isConflict
|
||||
on:renamed={() => {
|
||||
listUsers()
|
||||
}}
|
||||
/>
|
||||
{/key}
|
||||
{/if}
|
||||
</td>
|
||||
{/if}
|
||||
<td>
|
||||
<ToggleButtonGroup
|
||||
selected={super_admin}
|
||||
@@ -143,9 +220,22 @@
|
||||
</ToggleButtonGroup>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex flex-row gap-x-1">
|
||||
<button
|
||||
class="text-red-500 whitespace-nowrap"
|
||||
<div class="flex flex-row gap-x-1 justify-end">
|
||||
{#if automateUsernameCreation && username}
|
||||
<ChangeInstanceUsername
|
||||
{username}
|
||||
{email}
|
||||
on:renamed={() => {
|
||||
listUsers()
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<Button
|
||||
color="light"
|
||||
variant="contained"
|
||||
size="xs"
|
||||
spacingSize="xs2"
|
||||
btnClasses="text-red-500"
|
||||
on:click={() => {
|
||||
deleteConfirmedCallback = async () => {
|
||||
console.log(email)
|
||||
@@ -156,7 +246,7 @@
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -112,14 +112,14 @@
|
||||
<DrawerContent title="User Settings" on:close={closeDrawer}>
|
||||
<div class="flex flex-col h-full">
|
||||
<div>
|
||||
<div class="text-xs pt-1 pb-2 text-tertiary flex-col flex">
|
||||
Windmill <Version />
|
||||
</div>
|
||||
<div class="font-semibold flex items-baseline">
|
||||
Theme: <DarkModeToggle forcedDarkMode={false} />
|
||||
</div>
|
||||
<div class="flex flex-wrap md:gap-40 gap-10 mt-2">
|
||||
{#if scopes == undefined}
|
||||
{#if scopes == undefined}
|
||||
<div class="text-xs pt-1 pb-2 text-tertiary flex-col flex">
|
||||
Windmill <Version />
|
||||
</div>
|
||||
<div class="font-semibold flex items-baseline">
|
||||
Theme: <DarkModeToggle forcedDarkMode={false} />
|
||||
</div>
|
||||
<div class="flex flex-wrap md:gap-40 gap-10 mt-2">
|
||||
<div>
|
||||
<h2 class="border-b">User info</h2>
|
||||
<div class="">
|
||||
@@ -167,50 +167,55 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div>
|
||||
<h2 class="border-b">AI user settings</h2>
|
||||
|
||||
<div class="flex flex-col gap-4 mt-2">
|
||||
<Toggle
|
||||
on:change={(e) => {
|
||||
updateSetting(
|
||||
codeCompletionSessionEnabled,
|
||||
e.detail,
|
||||
'codeCompletionSessionEnabled'
|
||||
)
|
||||
}}
|
||||
checked={$codeCompletionSessionEnabled}
|
||||
options={{
|
||||
right: 'Code completion',
|
||||
rightTooltip: 'AI completion in the code editors'
|
||||
}}
|
||||
/>
|
||||
<Toggle
|
||||
on:change={(e) => {
|
||||
updateSetting(metadataCompletionEnabled, e.detail, 'metadataCompletionEnabled')
|
||||
}}
|
||||
checked={$metadataCompletionEnabled}
|
||||
options={{
|
||||
right: 'Metadata completion',
|
||||
rightTooltip: 'AI completion for summaries and descriptions'
|
||||
}}
|
||||
/>
|
||||
<Toggle
|
||||
on:change={(e) => {
|
||||
updateSetting(stepInputCompletionEnabled, e.detail, 'stepInputCompletionEnabled')
|
||||
}}
|
||||
checked={$stepInputCompletionEnabled}
|
||||
options={{
|
||||
right: 'Flow step input completion',
|
||||
rightTooltip: 'AI completion for flow step inputs'
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<h2 class="border-b">AI user settings</h2>
|
||||
|
||||
<div class="flex flex-col gap-4 mt-2">
|
||||
<Toggle
|
||||
on:change={(e) => {
|
||||
updateSetting(
|
||||
codeCompletionSessionEnabled,
|
||||
e.detail,
|
||||
'codeCompletionSessionEnabled'
|
||||
)
|
||||
}}
|
||||
checked={$codeCompletionSessionEnabled}
|
||||
options={{
|
||||
right: 'Code completion',
|
||||
rightTooltip: 'AI completion in the code editors'
|
||||
}}
|
||||
/>
|
||||
<Toggle
|
||||
on:change={(e) => {
|
||||
updateSetting(metadataCompletionEnabled, e.detail, 'metadataCompletionEnabled')
|
||||
}}
|
||||
checked={$metadataCompletionEnabled}
|
||||
options={{
|
||||
right: 'Metadata completion',
|
||||
rightTooltip: 'AI completion for summaries and descriptions'
|
||||
}}
|
||||
/>
|
||||
<Toggle
|
||||
on:change={(e) => {
|
||||
updateSetting(
|
||||
stepInputCompletionEnabled,
|
||||
e.detail,
|
||||
'stepInputCompletionEnabled'
|
||||
)
|
||||
}}
|
||||
checked={$stepInputCompletionEnabled}
|
||||
options={{
|
||||
right: 'Flow step input completion',
|
||||
rightTooltip: 'AI completion for flow step inputs'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid grid-cols-2 pt-8 pb-1">
|
||||
<div class="grid grid-cols-2 pt-8 pb-1" class:pt-8={scopes == undefined}>
|
||||
<h2 class="py-0 my-0 border-b pt-3">Tokens</h2>
|
||||
<div class="flex justify-end border-b pb-1">
|
||||
<Button
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation'
|
||||
|
||||
import { UserService, WorkspaceService } from '$lib/gen'
|
||||
import { SettingService, UserService, WorkspaceService } from '$lib/gen'
|
||||
import { validateUsername } from '$lib/utils'
|
||||
import { page } from '$app/stores'
|
||||
import { usersWorkspaceStore, workspaceStore } from '$lib/stores'
|
||||
@@ -18,7 +18,7 @@
|
||||
async function acceptInvite(): Promise<void> {
|
||||
await UserService.acceptInvite({
|
||||
requestBody: {
|
||||
username,
|
||||
username: automateUsernameCreation ? undefined : username,
|
||||
workspace_id
|
||||
}
|
||||
})
|
||||
@@ -46,33 +46,44 @@
|
||||
}
|
||||
}
|
||||
|
||||
UserService.globalWhoami().then((x) => {
|
||||
let uname = ''
|
||||
if (x.name) {
|
||||
uname = x.name.split(' ')[0]
|
||||
} else {
|
||||
uname = x.email.split('@')[0]
|
||||
let automateUsernameCreation = false
|
||||
async function getAutomateUsernameCreationSetting() {
|
||||
automateUsernameCreation =
|
||||
(await SettingService.getGlobal({ key: 'automate_username_creation' })) ?? false
|
||||
|
||||
if (!automateUsernameCreation) {
|
||||
UserService.globalWhoami().then((x) => {
|
||||
let uname = ''
|
||||
if (x.name) {
|
||||
uname = x.name.split(' ')[0]
|
||||
} else {
|
||||
uname = x.email.split('@')[0]
|
||||
}
|
||||
username = uname.toLowerCase()
|
||||
})
|
||||
}
|
||||
username = uname.toLowerCase()
|
||||
})
|
||||
}
|
||||
getAutomateUsernameCreationSetting()
|
||||
</script>
|
||||
|
||||
<!-- Enable submit form on enter -->
|
||||
|
||||
<CenteredModal title="Invitation to join {workspace_id}">
|
||||
<label class="block pb-2">
|
||||
<span class="text-secondary text-sm">Your username in workspace {workspace_id}:</span>
|
||||
<input on:keyup={handleKey} bind:value={username} class:input-error={errorUsername != ''} />
|
||||
{#if errorUsername}
|
||||
<span class="text-red-500 text-xs">{errorUsername}</span>
|
||||
{/if}
|
||||
</label>
|
||||
{#if !automateUsernameCreation}
|
||||
<label class="block pb-2">
|
||||
<span class="text-secondary text-sm">Your username in workspace {workspace_id}:</span>
|
||||
<input on:keyup={handleKey} bind:value={username} class:input-error={errorUsername != ''} />
|
||||
{#if errorUsername}
|
||||
<span class="text-red-500 text-xs">{errorUsername}</span>
|
||||
{/if}
|
||||
</label>
|
||||
{/if}
|
||||
<div class="flex flex-row justify-between pt-4 gap-x-1">
|
||||
<Button variant="border" size="sm" href="/user/workspaces"
|
||||
>← Back to workspaces</Button
|
||||
>
|
||||
<button
|
||||
disabled={checking || errorUsername != '' || !username}
|
||||
disabled={checking || (!automateUsernameCreation && (errorUsername != '' || !username))}
|
||||
class="place-items-end bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 border rounded"
|
||||
type="button"
|
||||
on:click={acceptInvite}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation'
|
||||
import { ResourceService, UserService, WorkspaceService } from '$lib/gen'
|
||||
import { ResourceService, SettingService, UserService, WorkspaceService } from '$lib/gen'
|
||||
import { validateUsername } from '$lib/utils'
|
||||
import { logoutWithRedirect } from '$lib/logout'
|
||||
import { page } from '$app/stores'
|
||||
@@ -52,7 +52,7 @@
|
||||
requestBody: {
|
||||
id,
|
||||
name,
|
||||
username
|
||||
username: automateUsernameCreation ? undefined : username
|
||||
}
|
||||
})
|
||||
if (auto_invite) {
|
||||
@@ -62,7 +62,14 @@
|
||||
})
|
||||
}
|
||||
if (openAiKey != '') {
|
||||
let path = `u/${username}/openai_windmill_codegen`
|
||||
let actualUsername = username
|
||||
if (automateUsernameCreation) {
|
||||
const user = await UserService.whoami({
|
||||
workspace: id
|
||||
})
|
||||
actualUsername = user.username
|
||||
}
|
||||
let path = `u/${actualUsername}/openai_windmill_codegen`
|
||||
await ResourceService.createResource({
|
||||
workspace: id,
|
||||
requestBody: {
|
||||
@@ -107,20 +114,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
let automateUsernameCreation = false
|
||||
async function getAutomateUsernameCreationSetting() {
|
||||
automateUsernameCreation =
|
||||
(await SettingService.getGlobal({ key: 'automate_username_creation' })) ?? false
|
||||
|
||||
if (!automateUsernameCreation) {
|
||||
UserService.globalWhoami().then((x) => {
|
||||
let uname = ''
|
||||
if (x.name) {
|
||||
uname = x.name.split(' ')[0]
|
||||
} else {
|
||||
uname = x.email.split('@')[0]
|
||||
}
|
||||
uname = uname.replace(/\./gi, '')
|
||||
username = uname.toLowerCase()
|
||||
})
|
||||
}
|
||||
}
|
||||
getAutomateUsernameCreationSetting()
|
||||
|
||||
onMount(() => {
|
||||
loadWorkspaces()
|
||||
|
||||
UserService.globalWhoami().then((x) => {
|
||||
let uname = ''
|
||||
if (x.name) {
|
||||
uname = x.name.split(' ')[0]
|
||||
} else {
|
||||
uname = x.email.split('@')[0]
|
||||
}
|
||||
uname = uname.replace(/\./gi, '')
|
||||
username = uname.toLowerCase()
|
||||
})
|
||||
|
||||
WorkspaceService.isDomainAllowed().then((x) => {
|
||||
isDomainAllowed = x
|
||||
})
|
||||
@@ -139,7 +155,7 @@
|
||||
<label class="block pb-4 pt-4">
|
||||
<span class="text-secondary text-sm">Workspace name</span>
|
||||
<span class="ml-4 text-tertiary text-xs">Displayable name</span>
|
||||
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<input autofocus type="text" bind:value={name} />
|
||||
</label>
|
||||
<label class="block pb-4">
|
||||
@@ -150,13 +166,15 @@
|
||||
{/if}
|
||||
<input type="text" bind:value={id} class:input-error={errorId != ''} />
|
||||
</label>
|
||||
<label class="block pb-4">
|
||||
<span class="text-secondary text-sm">Your username in that workspace</span>
|
||||
<input type="text" bind:value={username} on:keyup={handleKeyUp} />
|
||||
{#if errorUser}
|
||||
<span class="text-red-500 text-xs">{errorUser}</span>
|
||||
{/if}
|
||||
</label>
|
||||
{#if !automateUsernameCreation}
|
||||
<label class="block pb-4">
|
||||
<span class="text-secondary text-sm">Your username in that workspace</span>
|
||||
<input type="text" bind:value={username} on:keyup={handleKeyUp} />
|
||||
{#if errorUser}
|
||||
<span class="text-red-500 text-xs">{errorUser}</span>
|
||||
{/if}
|
||||
</label>
|
||||
{/if}
|
||||
<label class="block pb-4">
|
||||
<span class="text-secondary text-sm">
|
||||
OpenAI key for Windmill AI
|
||||
@@ -226,7 +244,11 @@
|
||||
>← Back to workspaces</Button
|
||||
>
|
||||
<Button
|
||||
disabled={checking || errorId != '' || errorUser != '' || !name || !username || !id}
|
||||
disabled={checking ||
|
||||
errorId != '' ||
|
||||
!name ||
|
||||
(!automateUsernameCreation && (errorUser != '' || !username)) ||
|
||||
!id}
|
||||
on:click={createWorkspace}
|
||||
>
|
||||
Create workspace
|
||||
|
||||
Reference in New Issue
Block a user