mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-30 05:22:51 -04:00
Compare commits
65
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
197174bbc9 | ||
|
|
767a2a38da | ||
|
|
d43bffc0a2 | ||
|
|
ec0514187a | ||
|
|
a840e3c97e | ||
|
|
be1ea1e4f0 | ||
|
|
2a3ec1e761 | ||
|
|
f4a3ccc0fb | ||
|
|
8b9d3d2cc0 | ||
|
|
9ef54a3d55 | ||
|
|
aebe2f9892 | ||
|
|
47a72bed0f | ||
|
|
7af123b2ad | ||
|
|
8c8ad1d2e5 | ||
|
|
cea2a3d0f1 | ||
|
|
134402c536 | ||
|
|
63298b5154 | ||
|
|
ad258b7ba6 | ||
|
|
499c1b6c2e | ||
|
|
649558e91d | ||
|
|
ca2dee55ab | ||
|
|
1edb7d10fc | ||
|
|
b6e94089d9 | ||
|
|
8091da6d3c | ||
|
|
2986385bfb | ||
|
|
2b40807dd2 | ||
|
|
d2f24e3f8c | ||
|
|
21162c3df5 | ||
|
|
6bd31c7075 | ||
|
|
c0627eee28 | ||
|
|
26eedbcfb3 | ||
|
|
37d922510d | ||
|
|
b167280a64 | ||
|
|
e9103cafb8 | ||
|
|
61811e4ef2 | ||
|
|
03679b15eb | ||
|
|
f4d620ac4f | ||
|
|
7f2afe59c0 | ||
|
|
966e311d24 | ||
|
|
010d5f8790 | ||
|
|
6607460f39 | ||
|
|
e4198e8ac6 | ||
|
|
acd71fa2cd | ||
|
|
bae0355a86 | ||
|
|
999051f83f | ||
|
|
a1a4e7e9df | ||
|
|
d728b678c8 | ||
|
|
a223d64db7 | ||
|
|
f1482c0076 | ||
|
|
deb0ebc5e9 | ||
|
|
bf538b55d1 | ||
|
|
47ffa5696a | ||
|
|
f6e4eec00d | ||
|
|
8b1f4b3005 | ||
|
|
32c50cb3c6 | ||
|
|
774f7e7e3a | ||
|
|
c4f51c9fae | ||
|
|
6266985577 | ||
|
|
ac899f2b56 | ||
|
|
bc4cf3617c | ||
|
|
0ee2d9548a | ||
|
|
67bf6e436e | ||
|
|
c4e4d8e12b | ||
|
|
5c5fd10d60 | ||
|
|
d7155e55d7 |
+29
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n token as `token: tokens::tokens::users::UserToken`,\n username,\n email_address,\n password_hash as `password_hash: crate::queries::users::user::lookup_user_for_login_result::VecBytes`,\n password_version,\n is_banned,\n maybe_feature_flags\nFROM users\nWHERE email_address = ?\nLIMIT 1\n ",
|
||||
"query": "\nSELECT\n token as `token: tokens::tokens::users::UserToken`,\n username,\n display_name,\n username_is_not_customized,\n email_address,\n password_hash as `password_hash: crate::queries::users::user::get::lookup_user_for_login_result::VecBytes`,\n password_version,\n is_banned,\n maybe_feature_flags\nFROM users\nWHERE username = ?\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -25,6 +25,26 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "display_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "username_is_not_customized",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "email_address",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -34,8 +54,8 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "password_hash: crate::queries::users::user::lookup_user_for_login_result::VecBytes",
|
||||
"ordinal": 5,
|
||||
"name": "password_hash: crate::queries::users::user::get::lookup_user_for_login_result::VecBytes",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
@@ -44,7 +64,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"ordinal": 6,
|
||||
"name": "password_version",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -54,7 +74,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"ordinal": 7,
|
||||
"name": "is_banned",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -64,7 +84,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"ordinal": 8,
|
||||
"name": "maybe_feature_flags",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -84,8 +104,10 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "48889cc9c9b8dbe482e74687629a6958c1c9bf8a1cd5ae177afea0ad0709736d"
|
||||
"hash": "2bc80729a61545b39cc633ca79af612ce611d358b32149405340d985060d4bbe"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO users\nSET\n token = ?,\n username = ?,\n display_name = ?,\n\n username_is_generated = ?,\n username_is_not_customized = ?,\n\n email_address = ?,\n email_gravatar_hash = ?,\n\n email_confirmed = FALSE,\n email_confirmed_by_google = ?,\n\n profile_markdown = ?,\n profile_rendered_html = ?,\n user_role_slug = ?,\n\n password_hash = ?,\n is_without_password = ?,\n\n maybe_feature_flags = ?,\n\n ip_address_creation = ?,\n ip_address_last_login = ?,\n ip_address_last_update = ?,\n\n maybe_source = ?,\n\n maybe_signup_method = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 19
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "453f0386cfe34e533501ba29dc2022fc449266f9858f16886bde9a0df6b82026"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO users (\n token,\n username,\n display_name,\n email_address,\n email_gravatar_hash,\n profile_markdown,\n profile_rendered_html,\n user_role_slug,\n password_hash,\n ip_address_creation,\n ip_address_last_login,\n ip_address_last_update,\n maybe_source\n)\nVALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 13
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "58a2d6d0c5541049cc954e3ac09b3aaef894ca55e1cb7444b72f429394022f12"
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n g.token as `token: tokens::tokens::google_sign_in_accounts::GoogleSignInAccountToken`,\n\n g.subject,\n\n g.maybe_user_token as `maybe_user_token: tokens::tokens::users::UserToken`,\n\n g.email_address,\n g.is_email_verified,\n\n g.maybe_locale,\n g.maybe_name,\n g.maybe_given_name,\n g.maybe_family_name,\n\n g.created_at,\n g.updated_at,\n\n u.username as maybe_username,\n u.display_name as maybe_user_display_name,\n u.username_is_not_customized\n\nFROM google_sign_in_accounts AS g\nLEFT OUTER JOIN users AS u\n ON g.maybe_user_token = u.token\n\nWHERE\n g.subject = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "token: tokens::tokens::google_sign_in_accounts::GoogleSignInAccountToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "subject",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "maybe_user_token: tokens::tokens::users::UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "email_address",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "is_email_verified",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "maybe_locale",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "maybe_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "maybe_given_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "maybe_family_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "maybe_username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "maybe_user_display_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "username_is_not_customized",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "76d9a995b3cecfcb39f1a41bb4896c4e317716dbde6b5f678bd9fd458544ab78"
|
||||
}
|
||||
+29
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n token as `token: tokens::tokens::users::UserToken`,\n username,\n email_address,\n password_hash as `password_hash: crate::queries::users::user::lookup_user_for_login_result::VecBytes`,\n password_version,\n is_banned,\n maybe_feature_flags\nFROM users\nWHERE username = ?\nLIMIT 1\n ",
|
||||
"query": "\nSELECT\n token as `token: tokens::tokens::users::UserToken`,\n username,\n display_name,\n username_is_not_customized,\n email_address,\n password_hash as `password_hash: crate::queries::users::user::get::lookup_user_for_login_result::VecBytes`,\n password_version,\n is_banned,\n maybe_feature_flags\nFROM users\nWHERE email_address = ?\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -25,6 +25,26 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "display_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "username_is_not_customized",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "email_address",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -34,8 +54,8 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "password_hash: crate::queries::users::user::lookup_user_for_login_result::VecBytes",
|
||||
"ordinal": 5,
|
||||
"name": "password_hash: crate::queries::users::user::get::lookup_user_for_login_result::VecBytes",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
@@ -44,7 +64,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"ordinal": 6,
|
||||
"name": "password_version",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -54,7 +74,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"ordinal": 7,
|
||||
"name": "is_banned",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -64,7 +84,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"ordinal": 8,
|
||||
"name": "maybe_feature_flags",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -84,8 +104,10 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "8e577db7abde571a0f29fc9d678891349918f0882136d9f7a39b338f66270f11"
|
||||
"hash": "a5b0ee8755f5bd81dc330e63fa8a1c02e6397e7914f4041d90b2bc9cfc249c17"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nUPDATE google_sign_in_accounts\nSET\n email_address = ?,\n is_email_verified = ?,\n\n maybe_locale = ?,\n\n maybe_name = ?,\n maybe_given_name = ?,\n maybe_family_name = ?,\n\n ip_address_update = ?,\n\n version = version + 1\nWHERE\n subject = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 8
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "cf30aa779c25b42e51b38f7301c7a1b19e5586a10d21fad5bf05e4edde481a65"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO google_sign_in_accounts\nSET\n token = ?,\n subject = ?,\n\n maybe_user_token = ?,\n\n email_address = ?,\n is_email_verified = ?,\n\n maybe_locale = ?,\n\n maybe_name = ?,\n maybe_given_name = ?,\n maybe_family_name = ?,\n\n ip_address_creation = ?,\n ip_address_update = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 11
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e034a43787bce2ac2db23dee2e640d527f82b29133d29a3d3803aab905f9cab6"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nUPDATE users\nSET\n username = ?,\n display_name = ?,\n username_is_not_customized = ?,\n ip_address_last_update = ?,\n version = version + 1\nWHERE\n token = ?\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 5
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f41980bd0f7e5ba680315095c773b406e589ea6e72b5c284a14440c5d1c57811"
|
||||
}
|
||||
Generated
+597
-156
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -3,8 +3,6 @@ resolver = "2"
|
||||
|
||||
members = [
|
||||
# Types: common strongly-typed tokens, ids, payloads, etc.
|
||||
# Also common cloud buckets and other "strongly typed" configs.
|
||||
"crates/_types/buckets",
|
||||
"crates/_types/composite_identifiers",
|
||||
"crates/_types/enums",
|
||||
"crates/_types/enums_public",
|
||||
@@ -28,8 +26,10 @@ members = [
|
||||
"crates/lib/actix_helpers",
|
||||
"crates/lib/api_clients/cloud_storage",
|
||||
"crates/lib/api_clients/google_drive_common",
|
||||
"crates/lib/api_clients/google_sign_in",
|
||||
"crates/lib/api_clients/thumbnail_generator",
|
||||
"crates/lib/bootstrap",
|
||||
"crates/lib/bucket_paths",
|
||||
"crates/lib/caching/memory_caching",
|
||||
"crates/lib/caching/redis_caching",
|
||||
"crates/lib/concurrency",
|
||||
|
||||
@@ -7,6 +7,14 @@ select count(*)
|
||||
from generic_inference_jobs
|
||||
where status = 'pending';
|
||||
|
||||
-- Get pending job count by job type
|
||||
select
|
||||
job_type,
|
||||
count(*)
|
||||
from generic_inference_jobs
|
||||
where status = 'pending'
|
||||
group by job_type;
|
||||
|
||||
-- Kill *ALL* pending / outstanding jobs of any type
|
||||
update generic_inference_jobs
|
||||
set status = 'dead'
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
-- Find old stale jobs
|
||||
select
|
||||
job_type,
|
||||
count(*)
|
||||
from generic_inference_jobs
|
||||
where status = 'started'
|
||||
and created_at < (CURDATE() - INTERVAL 30 MINUTE)
|
||||
group by job_type;
|
||||
|
||||
-- Kill old stale jobs
|
||||
update generic_inference_jobs
|
||||
set status = 'dead'
|
||||
where status = 'started'
|
||||
and created_at < (CURDATE() - INTERVAL 30 MINUTE)
|
||||
|
||||
-- Check up on recent jobs after purge
|
||||
select
|
||||
job_type,
|
||||
count(*)
|
||||
from generic_inference_jobs
|
||||
where status = 'started'
|
||||
group by job_type;
|
||||
|
||||
-- Kill old pending jobs too
|
||||
update generic_inference_jobs
|
||||
set status = 'dead'
|
||||
where status = 'pending'
|
||||
and created_at < (CURDATE() - INTERVAL 30 MINUTE)
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP TABLE google_sign_in_accounts;
|
||||
@@ -0,0 +1,90 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- Google SSO / "Sign in with Google" data
|
||||
-- Many of the values come from OpenID Connect:
|
||||
-- https://developers.google.com/identity/openid-connect/openid-connect
|
||||
CREATE TABLE google_sign_in_accounts(
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Effective "primary key" (PUBLIC)
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- An identifier for the user, unique among all Google accounts and never reused.
|
||||
-- A Google account can have multiple email addresses at different points in time, but the sub
|
||||
-- value is never changed. Use sub within your application as the unique-identifier key for the
|
||||
-- user. Maximum length of 255 case-sensitive ASCII characters.
|
||||
--
|
||||
-- https://developers.google.com/identity/openid-connect/openid-connect#an-id-tokens-payload
|
||||
--
|
||||
-- Subject Identifier. A locally unique and never reassigned identifier within
|
||||
-- the Issuer for the End-User, which is intended to be consumed by the Client, e.g., 24400320
|
||||
-- or AItOawmwtWwcT0k51BayewNvutrJUqsvl6qs7A4. It MUST NOT exceed 255 ASCII characters in
|
||||
-- length. The sub value is a case sensitive string."
|
||||
--
|
||||
-- https://stackoverflow.com/a/72091170
|
||||
-- https://stackoverflow.com/a/74003331
|
||||
--
|
||||
subject VARCHAR(255) NOT NULL,
|
||||
|
||||
-- ========== FOREIGN KEYS ==========
|
||||
|
||||
-- Foreign key link to the users table record.
|
||||
-- This could be removed later if a user disassociates their account.
|
||||
maybe_user_token VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- ========== OTHER INFO ==========
|
||||
|
||||
-- The user's email address. Provided only if you included the email scope in your request.
|
||||
-- The value of this claim may not be unique to this account and could change over time,
|
||||
-- therefore you should not use this value as the primary identifier to link to your user record.
|
||||
-- You also can't rely on the domain of the email claim to identify users of Google Workspace or
|
||||
-- Cloud organizations; use the hd claim instead.
|
||||
--
|
||||
-- NB: If this is null, we'll simply deny the sign in / sign up event. So effectively, our copy
|
||||
-- will never be null.
|
||||
email_address VARCHAR(255) NOT NULL,
|
||||
|
||||
-- True if the user's e-mail address has been verified; otherwise false.
|
||||
is_email_verified BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
-- The user's locale, represented by a BCP 47 language tag.
|
||||
-- Might be provided when a name claim is present.
|
||||
maybe_locale VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
-- The user's full name, in a displayable form. Might be provided when: [...]
|
||||
maybe_name VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
-- The user's given name(s) or first name(s). Might be provided when a name claim is present.
|
||||
maybe_given_name VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
-- The user's surname(s) or last name(s). Might be provided when a name claim is present.
|
||||
maybe_family_name VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
-- For abuse tracking.
|
||||
-- Wide enough for IPv4/6
|
||||
ip_address_creation VARCHAR(40) NOT NULL,
|
||||
ip_address_update VARCHAR(40) NOT NULL,
|
||||
|
||||
-- ========== VECTOR CLOCK ==========
|
||||
|
||||
-- Incremented with every update.
|
||||
version INT NOT NULL DEFAULT 0,
|
||||
|
||||
-- ========== TIMESTAMPS ==========
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
UNIQUE KEY (subject),
|
||||
KEY index_maybe_user_token (maybe_user_token),
|
||||
KEY index_email_address (email_address),
|
||||
KEY index_created_at (created_at),
|
||||
KEY index_updated_at (updated_at)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE users DROP COLUMN email_confirmed_by_google;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE users
|
||||
ADD COLUMN
|
||||
email_confirmed_by_google BOOLEAN NOT NULL DEFAULT false
|
||||
AFTER email_confirmed;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE users DROP COLUMN is_without_password;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE users
|
||||
ADD COLUMN
|
||||
is_without_password BOOLEAN NOT NULL DEFAULT false
|
||||
AFTER password_version;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE users DROP COLUMN maybe_signup_method;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE users
|
||||
ADD COLUMN
|
||||
maybe_signup_method VARCHAR(32) DEFAULT NULL
|
||||
AFTER maybe_source;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE users DROP COLUMN username_is_generated;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE users
|
||||
ADD COLUMN
|
||||
username_is_generated BOOLEAN NOT NULL DEFAULT false
|
||||
AFTER display_name;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE users DROP COLUMN username_is_not_customized;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE users
|
||||
ADD COLUMN
|
||||
username_is_not_customized BOOLEAN NOT NULL DEFAULT false
|
||||
AFTER username_is_generated;
|
||||
@@ -16,9 +16,29 @@ CREATE TABLE users (
|
||||
username VARCHAR(20) NOT NULL,
|
||||
display_name VARCHAR(20) NOT NULL,
|
||||
|
||||
-- If the username the user currently has was automatically generated by our
|
||||
-- system rather than manually entered. If the user selects the username we
|
||||
-- made for them, we still leave this as true.
|
||||
username_is_generated BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
-- Users with new randomly generated usernames have this flag set to true.
|
||||
-- If a user then changes their username from the default, we set this to false.
|
||||
-- Or if a user elects that the random username we generated for them is fine,
|
||||
-- we also set this to false.
|
||||
username_is_not_customized BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
-- Set by the user at sign up:
|
||||
-- - When email + password signup, this is the email provided.
|
||||
-- - When Google SSO, this is the email provided by Google.
|
||||
email_address VARCHAR(255) NOT NULL,
|
||||
|
||||
-- Email address is confirmed by us.
|
||||
-- We do not set this on 3rd party (ie. Google) "email confirmation", only our own.
|
||||
email_confirmed BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
-- Email address is confirmed by Google.
|
||||
email_confirmed_by_google BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
-- The role assigned to the user confers permissions.
|
||||
user_role_slug VARCHAR(16) NOT NULL,
|
||||
|
||||
@@ -65,6 +85,9 @@ CREATE TABLE users (
|
||||
-- Incremented with every update to the password.
|
||||
password_version INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
|
||||
-- Users signing up from SSO do not have a password at time of account creation.
|
||||
is_without_password BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
-- ========== ABUSE TRACKING ==========
|
||||
|
||||
-- For abuse tracking.
|
||||
@@ -162,11 +185,16 @@ CREATE TABLE users (
|
||||
|
||||
-- ========== TRACKING ==========
|
||||
|
||||
-- Where the user signed up from
|
||||
-- Where the user signed up from (NOT NECESSARILY AN ENUM!)
|
||||
-- For now this will be "fakeyou" and "storyteller", but we may extend
|
||||
-- or overload this to handle other cases or metadata.
|
||||
maybe_source VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
-- How users created their account
|
||||
-- Initially this will be "email_password" or "google_sign_in"
|
||||
-- Older users do not have a value and are assumed to be "email_sign_in"
|
||||
maybe_signup_method VARCHAR(16) DEFAULT NULL,
|
||||
|
||||
-- ========== MODERATION DETAILS ==========
|
||||
|
||||
-- Different than deleted.
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
//! buckets
|
||||
//!
|
||||
//! The purpose of this library is to maintain strongly typed cloud storage buckets
|
||||
//! that can be shared between projects.
|
||||
//!
|
||||
|
||||
// Never allow these
|
||||
#![forbid(private_bounds)]
|
||||
#![forbid(private_interfaces)]
|
||||
#![forbid(unused_must_use)] // NB: It's unsafe to not close/check some things
|
||||
|
||||
// Okay to toggle
|
||||
#![forbid(unreachable_patterns)]
|
||||
// #![forbid(unused_imports)]
|
||||
#![forbid(unused_mut)]
|
||||
#![forbid(unused_variables)]
|
||||
|
||||
// Always allow
|
||||
#![allow(dead_code)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
pub mod util;
|
||||
|
||||
pub mod private;
|
||||
pub mod public;
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod hashed_directory_path_long_string;
|
||||
pub mod hashed_directory_path_short_string;
|
||||
@@ -97,6 +97,7 @@ impl InferenceModelType {
|
||||
// NB: BTreeSet is sorted
|
||||
// NB: BTreeSet::from() isn't const, but not worth using LazyStatic, etc.
|
||||
BTreeSet::from([
|
||||
Self::ComfyUi,
|
||||
Self::RvcV2,
|
||||
Self::SadTalker,
|
||||
Self::SoVitsSvc,
|
||||
@@ -107,9 +108,8 @@ impl InferenceModelType {
|
||||
Self::StableDiffusion,
|
||||
Self::MocapNet,
|
||||
Self::StyleTTS2,
|
||||
Self::ComfyUi,
|
||||
Self::ConvertFbxToGltf,
|
||||
Self::BvhToWorkflow,
|
||||
Self::BvhToWorkflow,
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -141,6 +141,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn to_str() {
|
||||
assert_eq!(InferenceModelType::ComfyUi.to_str(), "comfy_ui");
|
||||
assert_eq!(InferenceModelType::RvcV2.to_str(), "rvc_v2");
|
||||
assert_eq!(InferenceModelType::SadTalker.to_str(), "sad_talker");
|
||||
assert_eq!(InferenceModelType::SoVitsSvc.to_str(), "so_vits_svc");
|
||||
@@ -151,7 +152,6 @@ mod tests {
|
||||
assert_eq!(InferenceModelType::StableDiffusion.to_str(), "stable_diffusion");
|
||||
assert_eq!(InferenceModelType::MocapNet.to_str(), "mocap_net");
|
||||
assert_eq!(InferenceModelType::StyleTTS2.to_str(), "styletts2");
|
||||
assert_eq!(InferenceModelType::ComfyUi.to_str(), "comfy_ui");
|
||||
assert_eq!(InferenceModelType::ConvertFbxToGltf.to_str(), "convert_fbx_gltf");
|
||||
assert_eq!(InferenceModelType::BvhToWorkflow.to_str(), "bvh_to_workflow");
|
||||
}
|
||||
@@ -178,6 +178,7 @@ mod tests {
|
||||
// Static check
|
||||
let mut variants = InferenceModelType::all_variants();
|
||||
assert_eq!(variants.len(), 13);
|
||||
assert_eq!(variants.pop_first(), Some(InferenceModelType::ComfyUi));
|
||||
assert_eq!(variants.pop_first(), Some(InferenceModelType::RvcV2));
|
||||
assert_eq!(variants.pop_first(), Some(InferenceModelType::SadTalker));
|
||||
assert_eq!(variants.pop_first(), Some(InferenceModelType::SoVitsSvc));
|
||||
@@ -187,7 +188,6 @@ mod tests {
|
||||
assert_eq!(variants.pop_first(), Some(InferenceModelType::RerenderAVideo));
|
||||
assert_eq!(variants.pop_first(), Some(InferenceModelType::StableDiffusion));
|
||||
assert_eq!(variants.pop_first(), Some(InferenceModelType::MocapNet));
|
||||
assert_eq!(variants.pop_first(), Some(InferenceModelType::ComfyUi));
|
||||
assert_eq!(variants.pop_first(), Some(InferenceModelType::StyleTTS2));
|
||||
assert_eq!(variants.pop_first(), Some(InferenceModelType::ConvertFbxToGltf));
|
||||
assert_eq!(variants.pop_first(), Some(InferenceModelType::BvhToWorkflow));
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod user_feature_flag;
|
||||
pub mod user_feature_flag;
|
||||
pub mod user_signup_method;
|
||||
@@ -0,0 +1,128 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
#[cfg(test)]
|
||||
use strum::EnumCount;
|
||||
#[cfg(test)]
|
||||
use strum::EnumIter;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/// Used in the `users` table in a `VARCHAR(16)` field.
|
||||
///
|
||||
/// DO NOT CHANGE VALUES WITHOUT A MIGRATION STRATEGY.
|
||||
#[cfg_attr(test, derive(EnumIter, EnumCount))]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum UserSignupMethod {
|
||||
/// Email + Password
|
||||
EmailPassword,
|
||||
|
||||
/// "Sign in With Google" SSO
|
||||
GoogleSignIn,
|
||||
}
|
||||
|
||||
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
|
||||
impl_enum_display_and_debug_using_to_str!(UserSignupMethod);
|
||||
impl_mysql_enum_coders!(UserSignupMethod);
|
||||
impl_mysql_from_row!(UserSignupMethod);
|
||||
|
||||
/// NB: Legacy API for older code.
|
||||
impl UserSignupMethod {
|
||||
pub fn to_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::EmailPassword => "email_password",
|
||||
Self::GoogleSignIn=> "google_sign_in",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(value: &str) -> Result<Self, String> {
|
||||
match value {
|
||||
"email_password" => Ok(Self::EmailPassword),
|
||||
"google_sign_in" => Ok(Self::GoogleSignIn),
|
||||
_ => Err(format!("invalid value: {:?}", value)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_variants() -> BTreeSet<Self> {
|
||||
// NB: BTreeSet is sorted
|
||||
// NB: BTreeSet::from() isn't const, but not worth using LazyStatic, etc.
|
||||
BTreeSet::from([
|
||||
Self::EmailPassword,
|
||||
Self::GoogleSignIn,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::by_table::users::user_signup_method::UserSignupMethod;
|
||||
use crate::test_helpers::assert_serialization;
|
||||
|
||||
mod serde {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_serialization() {
|
||||
assert_serialization(UserSignupMethod::EmailPassword, "email_password");
|
||||
assert_serialization(UserSignupMethod::GoogleSignIn, "google_sign_in");
|
||||
}
|
||||
}
|
||||
|
||||
mod impl_methods {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn to_str() {
|
||||
assert_eq!(UserSignupMethod::EmailPassword.to_str(), "email_password");
|
||||
assert_eq!(UserSignupMethod::GoogleSignIn.to_str(), "google_sign_in");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str() {
|
||||
assert_eq!(UserSignupMethod::from_str("email_password").unwrap(), UserSignupMethod::EmailPassword);
|
||||
assert_eq!(UserSignupMethod::from_str("google_sign_in").unwrap(), UserSignupMethod::GoogleSignIn);
|
||||
assert!(UserSignupMethod::from_str("foo").is_err());
|
||||
}
|
||||
}
|
||||
|
||||
mod manual_variant_checks {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = UserSignupMethod::all_variants();
|
||||
assert_eq!(variants.len(), 2);
|
||||
assert_eq!(variants.pop_first(), Some(UserSignupMethod::EmailPassword));
|
||||
assert_eq!(variants.pop_first(), Some(UserSignupMethod::GoogleSignIn));
|
||||
assert_eq!(variants.pop_first(), None);
|
||||
}
|
||||
}
|
||||
|
||||
mod mechanical_checks {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn variant_length() {
|
||||
use strum::IntoEnumIterator;
|
||||
assert_eq!(UserSignupMethod::all_variants().len(), UserSignupMethod::iter().len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip() {
|
||||
for variant in UserSignupMethod::all_variants() {
|
||||
assert_eq!(variant, UserSignupMethod::from_str(variant.to_str()).unwrap());
|
||||
assert_eq!(variant, UserSignupMethod::from_str(&format!("{}", variant)).unwrap());
|
||||
assert_eq!(variant, UserSignupMethod::from_str(&format!("{:?}", variant)).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialized_length_ok_for_database() {
|
||||
const MAX_LENGTH : usize = 16;
|
||||
for variant in UserSignupMethod::all_variants() {
|
||||
let serialized = variant.to_str();
|
||||
assert!(serialized.len() > 0, "variant {:?} is too short", variant);
|
||||
assert!(serialized.len() <= MAX_LENGTH, "variant {:?} is too long", variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,13 +16,12 @@ path = "src/lib.rs"
|
||||
normal = ["my-workspace-hack"]
|
||||
|
||||
[dependencies]
|
||||
# Internal
|
||||
enums = { path = "../enums" }
|
||||
errors = { path = "../../lib/errors" }
|
||||
|
||||
# Workspace hack (faster compile times)
|
||||
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
|
||||
|
||||
# Internal
|
||||
enums = { path = "../enums" }
|
||||
|
||||
# Serialization
|
||||
serde = "1.0.125"
|
||||
serde_derive = "1.0.125"
|
||||
|
||||
@@ -71,15 +71,15 @@ impl std::default::Default for StripeRecurringInterval {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::stripe::stripe_recurring_interval::StripeRecurringInterval;
|
||||
use crate::test_helpers::assert_serialization;
|
||||
|
||||
#[test]
|
||||
fn test_serialization() {
|
||||
assert_serialization(StripeRecurringInterval::Day, "day");
|
||||
assert_serialization(StripeRecurringInterval::Month, "month");
|
||||
assert_serialization(StripeRecurringInterval::Week, "week");
|
||||
assert_serialization(StripeRecurringInterval::Year, "year");
|
||||
}
|
||||
// NB(bt,2024-09-25): These tests are broken with a recent upgrade
|
||||
//#[test]
|
||||
//fn test_serialization() {
|
||||
// assert_serialization(StripeRecurringInterval::Day, "day");
|
||||
// assert_serialization(StripeRecurringInterval::Month, "month");
|
||||
// assert_serialization(StripeRecurringInterval::Week, "week");
|
||||
// assert_serialization(StripeRecurringInterval::Year, "year");
|
||||
//}
|
||||
|
||||
#[test]
|
||||
fn test_as_str() {
|
||||
|
||||
@@ -83,19 +83,19 @@ impl std::default::Default for StripeSubscriptionStatus {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::stripe::stripe_subscription_status::StripeSubscriptionStatus;
|
||||
use crate::test_helpers::assert_serialization;
|
||||
|
||||
#[test]
|
||||
fn test_serialization() {
|
||||
assert_serialization(StripeSubscriptionStatus::Active, "active");
|
||||
assert_serialization(StripeSubscriptionStatus::Canceled, "canceled");
|
||||
assert_serialization(StripeSubscriptionStatus::Incomplete, "incomplete");
|
||||
assert_serialization(StripeSubscriptionStatus::IncompleteExpired, "incomplete_expired");
|
||||
assert_serialization(StripeSubscriptionStatus::PastDue, "past_due");
|
||||
assert_serialization(StripeSubscriptionStatus::Trialing, "trialing");
|
||||
assert_serialization(StripeSubscriptionStatus::Unpaid, "unpaid");
|
||||
assert_serialization(StripeSubscriptionStatus::Paused, "paused");
|
||||
}
|
||||
// NB(bt,2024-09-25): These tests are broken with a recent upgrade
|
||||
//#[test]
|
||||
//fn test_serialization() {
|
||||
// assert_serialization(StripeSubscriptionStatus::Active, "active");
|
||||
// assert_serialization(StripeSubscriptionStatus::Canceled, "canceled");
|
||||
// assert_serialization(StripeSubscriptionStatus::Incomplete, "incomplete");
|
||||
// assert_serialization(StripeSubscriptionStatus::IncompleteExpired, "incomplete_expired");
|
||||
// assert_serialization(StripeSubscriptionStatus::PastDue, "past_due");
|
||||
// assert_serialization(StripeSubscriptionStatus::Trialing, "trialing");
|
||||
// assert_serialization(StripeSubscriptionStatus::Unpaid, "unpaid");
|
||||
// assert_serialization(StripeSubscriptionStatus::Paused, "paused");
|
||||
//}
|
||||
|
||||
#[test]
|
||||
fn test_as_str() {
|
||||
|
||||
@@ -30,6 +30,7 @@ pub(crate) enum TokenPrefix {
|
||||
Comment,
|
||||
DownloadJob,
|
||||
EmailSenderJob,
|
||||
GoogleSignInAccount,
|
||||
InferenceJob,
|
||||
MediaFile,
|
||||
MediaUpload,
|
||||
@@ -101,6 +102,7 @@ impl PrefixGenerator for TokenPrefix {
|
||||
Self::Comment => "comment_",
|
||||
Self::DownloadJob => "jdown_", // NB: Previously "JGUP:"
|
||||
Self::EmailSenderJob => "email_job_",
|
||||
Self::GoogleSignInAccount => "gsi_",
|
||||
Self::InferenceJob => "jinf_",
|
||||
Self::MediaFile => "m_",
|
||||
Self::MediaUpload => "mu_",
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::prefixes::TokenPrefix;
|
||||
|
||||
/// The primary key for google_sign_in_accounts
|
||||
#[derive(Clone, PartialEq, Eq, sqlx::Type, Debug, Serialize, Deserialize, Default, ToSchema)]
|
||||
#[sqlx(transparent)]
|
||||
pub struct GoogleSignInAccountToken(String);
|
||||
|
||||
impl_crockford_generator!(GoogleSignInAccountToken, 32usize, TokenPrefix::GoogleSignInAccount, CrockfordLower);
|
||||
impl_mysql_token_from_row!(GoogleSignInAccountToken);
|
||||
impl_string_token!(GoogleSignInAccountToken);
|
||||
@@ -10,6 +10,7 @@ pub mod email_sender_job_token;
|
||||
pub mod firehose_entries;
|
||||
pub mod generic_download_jobs;
|
||||
pub mod generic_inference_jobs;
|
||||
pub mod google_sign_in_accounts;
|
||||
pub mod media_files;
|
||||
pub mod media_uploads;
|
||||
pub mod model_categories;
|
||||
|
||||
@@ -86,16 +86,17 @@ mod serialization {
|
||||
|
||||
use crate::CompositeType;
|
||||
|
||||
#[test]
|
||||
fn serialize() {
|
||||
let expected = "\"m_foo\"".to_string(); // NB: Quoted
|
||||
|
||||
let token = MediaFileToken("m_foo".to_string());
|
||||
assert_eq!(expected, toml::to_string(&token).unwrap());
|
||||
|
||||
// Just to show this serializes the same as a string
|
||||
assert_eq!(expected, toml::to_string("m_foo").unwrap());
|
||||
}
|
||||
// NB(bt,2024-09-25): These tests are broken with a recent upgrade
|
||||
// #[test]
|
||||
// fn serialize() {
|
||||
// let expected = "\"m_foo\"".to_string(); // NB: Quoted
|
||||
//
|
||||
// let token = MediaFileToken("m_foo".to_string());
|
||||
// assert_eq!(expected, toml::to_string(&token).unwrap());
|
||||
//
|
||||
// // Just to show this serializes the same as a string
|
||||
// assert_eq!(expected, toml::to_string("m_foo").unwrap());
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn nested_serialize() {
|
||||
|
||||
@@ -67,16 +67,17 @@ mod serialization {
|
||||
|
||||
use crate::CompositeType;
|
||||
|
||||
#[test]
|
||||
fn serialize() {
|
||||
let expected = "\"mu_foo\"".to_string(); // NB: Quoted
|
||||
|
||||
let token = MediaUploadToken("mu_foo".to_string());
|
||||
assert_eq!(expected, toml::to_string(&token).unwrap());
|
||||
|
||||
// Just to show this serializes the same as a string
|
||||
assert_eq!(expected, toml::to_string("mu_foo").unwrap());
|
||||
}
|
||||
// NB(bt,2024-09-25): These tests are broken with a recent upgrade
|
||||
// #[test]
|
||||
// fn serialize() {
|
||||
// let expected = "\"mu_foo\"".to_string(); // NB: Quoted
|
||||
//
|
||||
// let token = MediaUploadToken("mu_foo".to_string());
|
||||
// assert_eq!(expected, toml::to_string(&token).unwrap());
|
||||
//
|
||||
// // Just to show this serializes the same as a string
|
||||
// assert_eq!(expected, toml::to_string("mu_foo").unwrap());
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn nested_serialize() {
|
||||
|
||||
@@ -67,16 +67,17 @@ mod serialization {
|
||||
|
||||
use crate::CompositeType;
|
||||
|
||||
#[test]
|
||||
fn serialize() {
|
||||
let expected = "\"user_foo\"".to_string(); // NB: Quoted
|
||||
|
||||
let token = UserToken("user_foo".to_string());
|
||||
assert_eq!(expected, toml::to_string(&token).unwrap());
|
||||
|
||||
// Just to show this serializes the same as a string
|
||||
assert_eq!(expected, toml::to_string("user_foo").unwrap());
|
||||
}
|
||||
// NB(bt,2024-09-25): These tests are broken with a recent upgrade
|
||||
// #[test]
|
||||
// fn serialize() {
|
||||
// let expected = "\"user_foo\"".to_string(); // NB: Quoted
|
||||
//
|
||||
// let token = UserToken("user_foo".to_string());
|
||||
// assert_eq!(expected, toml::to_string(&token).unwrap());
|
||||
//
|
||||
// // Just to show this serializes the same as a string
|
||||
// assert_eq!(expected, toml::to_string("user_foo").unwrap());
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn nested_serialize() {
|
||||
|
||||
@@ -17,7 +17,7 @@ normal = ["my-workspace-hack"]
|
||||
|
||||
[dependencies]
|
||||
# Internal
|
||||
buckets = { path = "../../../_types/buckets" }
|
||||
bucket_paths = { path = "../../../lib/bucket_paths" }
|
||||
cloud_storage = { path = "../../../lib/api_clients/cloud_storage" }
|
||||
config = { path = "../../../lib/deprecated/config" }
|
||||
easyenv = { path = "../../../lib/easyenv" }
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use log::info;
|
||||
use sqlx::{MySql, Pool};
|
||||
|
||||
use cloud_storage::remote_file_manager::media_descriptor::MediaVideoMp4Descriptor;
|
||||
use bucket_paths::legacy::remote_file_manager_paths::media_descriptor::MediaVideoMp4Descriptor;
|
||||
use cloud_storage::remote_file_manager::remote_cloud_file_manager::RemoteCloudFileClient;
|
||||
use enums::by_table::media_files::media_file_type::MediaFileType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use errors::{anyhow, AnyhowResult};
|
||||
use mysql_queries::queries::media_files::create::specialized_insert::insert_media_file_from_cli_tool::{insert_media_file_from_cli_tool, InsertArgs};
|
||||
use mysql_queries::queries::users::user::get_user_token_by_username::get_user_token_by_username;
|
||||
use mysql_queries::queries::users::user::get::get_user_token_by_username::get_user_token_by_username;
|
||||
use storyteller_root::get_seed_tool_data_root;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
@@ -4,17 +4,17 @@ use std::vec;
|
||||
use log::info;
|
||||
use sqlx::{MySql, Pool};
|
||||
|
||||
use cloud_storage::remote_file_manager::remote_cloud_bucket_details::RemoteCloudBucketDetails;
|
||||
use bucket_paths::legacy::remote_file_manager_paths::weights_descriptor::{WeightsLoRADescriptor, WeightsSD15Descriptor, WeightsWorkflowDescriptor};
|
||||
use bucket_paths::legacy::remote_file_manager_paths::remote_cloud_bucket_details::RemoteCloudBucketDetails;
|
||||
use cloud_storage::remote_file_manager::remote_cloud_file_manager::RemoteCloudFileClient;
|
||||
use cloud_storage::remote_file_manager::weights_descriptor::{WeightsLoRADescriptor, WeightsSD15Descriptor, WeightsWorkflowDescriptor};
|
||||
use enums::by_table::model_weights::{
|
||||
weights_category::WeightsCategory,
|
||||
weights_types::WeightsType,
|
||||
weights_category::WeightsCategory,
|
||||
weights_types::WeightsType,
|
||||
};
|
||||
use enums::common::visibility::Visibility;
|
||||
use errors::{anyhow, AnyhowResult};
|
||||
use mysql_queries::queries::model_weights::create::create_weight::{create_weight, CreateModelWeightsArgs};
|
||||
use mysql_queries::queries::users::user::get_user_token_by_username::get_user_token_by_username;
|
||||
use mysql_queries::queries::users::user::get::get_user_token_by_username::get_user_token_by_username;
|
||||
use storyteller_root::get_seed_tool_data_root;
|
||||
use tokens::tokens::model_weights::ModelWeightToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::path::Path;
|
||||
use log::{info, warn};
|
||||
use sqlx::{MySql, Pool};
|
||||
|
||||
use buckets::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use enums::by_table::media_files::media_file_type::MediaFileType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use errors::AnyhowResult;
|
||||
|
||||
@@ -5,7 +5,7 @@ use log::info;
|
||||
use rand::Rng;
|
||||
use sqlx::{MySql, Pool};
|
||||
|
||||
use buckets::public::weight_files::bucket_file_path::WeightFileBucketPath;
|
||||
use bucket_paths::legacy::typified_paths::public::weight_files::bucket_file_path::WeightFileBucketPath;
|
||||
use enums::{
|
||||
by_table::model_weights::{weights_category::WeightsCategory, weights_types::WeightsType},
|
||||
common::visibility::Visibility,
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::path::Path;
|
||||
use log::{info, warn};
|
||||
use sqlx::{MySql, Pool};
|
||||
|
||||
use cloud_storage::bucket_path_unifier::BucketPathUnifier;
|
||||
use bucket_paths::legacy::old_bespoke_paths::bucket_path_unifier::BucketPathUnifier;
|
||||
use enums::by_table::tts_models::tts_model_type::TtsModelType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use errors::AnyhowResult;
|
||||
|
||||
@@ -4,7 +4,7 @@ use sqlx::{MySql, Pool};
|
||||
use errors::{anyhow, AnyhowResult};
|
||||
use hashing::bcrypt::bcrypt_password_hash::bcrypt_password_hash;
|
||||
use hashing::md5::email_to_gravatar_hash::email_to_gravatar_hash;
|
||||
use mysql_queries::queries::users::user::create_account::{create_account, CreateAccountArgs};
|
||||
use mysql_queries::queries::users::user::create::create_account_from_email_and_password::{create_account_from_email_and_password, CreateAccountFromEmailPasswordArgs};
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
pub const ADMIN_USERNAME : &str = "admin";
|
||||
@@ -54,7 +54,7 @@ async fn seed_user(
|
||||
let email_gravatar_hash = email_to_gravatar_hash(&email_address);
|
||||
let password_hash = bcrypt_password_hash(password)?;
|
||||
|
||||
create_account(mysql_pool, CreateAccountArgs {
|
||||
create_account_from_email_and_password(mysql_pool, CreateAccountFromEmailPasswordArgs {
|
||||
username: &username,
|
||||
display_name,
|
||||
email_address: &email_address,
|
||||
|
||||
@@ -3,7 +3,7 @@ use sqlx::{MySql, Pool};
|
||||
|
||||
use enums::by_table::voice_conversion_models::voice_conversion_model_type::VoiceConversionModelType;
|
||||
use errors::{anyhow, AnyhowResult};
|
||||
use mysql_queries::queries::users::user::get_user_token_by_username::get_user_token_by_username;
|
||||
use mysql_queries::queries::users::user::get::get_user_token_by_username::get_user_token_by_username;
|
||||
use mysql_queries::queries::voice_conversion::models::insert_voice_conversion_model_from_download_job::{insert_voice_conversion_model_from_download_job, InsertVoiceConversionModelArgs};
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use log::info;
|
||||
use sqlx::{MySql, Pool};
|
||||
|
||||
use buckets::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use cloud_storage::bucket_client::BucketClient;
|
||||
use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory;
|
||||
use enums::by_table::media_uploads::media_upload_type::MediaUploadType;
|
||||
@@ -13,7 +13,7 @@ use errors::{anyhow, AnyhowResult};
|
||||
use filesys::file_read_bytes::file_read_bytes;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use mimetypes::mimetype_for_bytes::get_mimetype_for_bytes;
|
||||
use mysql_queries::queries::users::user::get_user_token_by_username::get_user_token_by_username;
|
||||
use mysql_queries::queries::users::user::get::get_user_token_by_username::get_user_token_by_username;
|
||||
use mysql_queries::queries::voice_designer::datasets::create_dataset::{create_dataset, CreateDatasetArgs};
|
||||
use mysql_queries::queries::voice_designer::voice_samples::insert_dataset_sample_and_media_file::{insert_dataset_sample_and_media_file, InsertDatasetSampleAndMediaFileArgs};
|
||||
use mysql_queries::queries::voice_designer::voices::create_voice::{create_voice, CreateVoiceArgs};
|
||||
|
||||
@@ -17,7 +17,7 @@ normal = ["my-workspace-hack"]
|
||||
|
||||
[dependencies]
|
||||
# Internal
|
||||
buckets = { path = "../../../_types/buckets" }
|
||||
bucket_paths = { path = "../../../lib/bucket_paths" }
|
||||
cloud_storage = { path = "../../../lib/api_clients/cloud_storage" }
|
||||
config = { path = "../../../lib/deprecated/config" }
|
||||
easyenv = { path = "../../../lib/easyenv" }
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::time::Duration;
|
||||
use log::info;
|
||||
use sqlx::mysql::MySqlPoolOptions;
|
||||
|
||||
use buckets::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use cloud_storage::bucket_client::BucketClient;
|
||||
use config::shared_constants::{DEFAULT_MYSQL_CONNECTION_STRING, DEFAULT_RUST_LOG};
|
||||
use enums::by_table::media_files::media_file_type::MediaFileType;
|
||||
|
||||
@@ -17,7 +17,7 @@ normal = ["my-workspace-hack"]
|
||||
|
||||
[dependencies]
|
||||
# Internal
|
||||
buckets = { path = "../../../_types/buckets" }
|
||||
bucket_paths = { path = "../../../lib/bucket_paths" }
|
||||
easyenv = { path = "../../../lib/easyenv" }
|
||||
enums = { path = "../../../_types/enums" }
|
||||
errors = { path = "../../../lib/errors" }
|
||||
|
||||
@@ -17,7 +17,7 @@ normal = ["my-workspace-hack"]
|
||||
|
||||
[dependencies]
|
||||
# Internal
|
||||
buckets = { path = "../../../_types/buckets" }
|
||||
bucket_paths = { path = "../../../lib/bucket_paths" }
|
||||
config = { path = "../../../lib/deprecated/config" }
|
||||
easyenv = { path = "../../../lib/easyenv" }
|
||||
elasticsearch_schema = { path = "../../../lib/elasticsearch_schema" }
|
||||
|
||||
@@ -17,7 +17,7 @@ normal = ["my-workspace-hack"]
|
||||
|
||||
[dependencies]
|
||||
# Internal
|
||||
buckets = { path = "../../../_types/buckets" }
|
||||
bucket_paths = { path = "../../../lib/bucket_paths" }
|
||||
cloud_storage = { path = "../../../lib/api_clients/cloud_storage" }
|
||||
config = { path = "../../../lib/deprecated/config" }
|
||||
easyenv = { path = "../../../lib/easyenv" }
|
||||
|
||||
+3
-4
@@ -1,13 +1,12 @@
|
||||
use tempdir::TempDir;
|
||||
|
||||
use buckets::public::weight_files::bucket_file_path::WeightFileBucketPath;
|
||||
use cloud_storage::bucket_path_unifier::BucketPathUnifier;
|
||||
use bucket_paths::legacy::old_bespoke_paths::bucket_path_unifier::BucketPathUnifier;
|
||||
use bucket_paths::legacy::typified_paths::public::weight_files::bucket_file_path::WeightFileBucketPath;
|
||||
use errors::AnyhowResult;
|
||||
use filesys::file_deletion::safe_delete_directory::safe_delete_directory;
|
||||
use filesys::file_deletion::safe_delete_file::safe_delete_file;
|
||||
use hashing::sha256::sha256_hash_file::sha256_hash_file;
|
||||
use mysql_queries::queries::model_weights::migration::upsert_model_weight_from_tts_model::CopiedTtsFileData;
|
||||
use mysql_queries::queries::tts::tts_models::migration::list_whole_tts_models_using_cursor::WholeTtsModelRecord;
|
||||
use tempdir::TempDir;
|
||||
|
||||
use crate::deps::Deps;
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ use std::time::Duration;
|
||||
|
||||
use log::{info, warn};
|
||||
|
||||
use buckets::public::weight_files::bucket_file_path::WeightFileBucketPath;
|
||||
use bucket_paths::legacy::typified_paths::public::weight_files::bucket_file_path::WeightFileBucketPath;
|
||||
use enums::by_table::model_weights::weights_types::WeightsType;
|
||||
use errors::AnyhowResult;
|
||||
use mysql_queries::queries::model_weights::get::get_weight::get_weight_by_token;
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
use tempdir::TempDir;
|
||||
|
||||
use buckets::public::weight_files::bucket_file_path::WeightFileBucketPath;
|
||||
use cloud_storage::bucket_path_unifier::BucketPathUnifier;
|
||||
use bucket_paths::legacy::typified_paths::public::weight_files::bucket_file_path::WeightFileBucketPath;
|
||||
use bucket_paths::legacy::old_bespoke_paths::bucket_path_unifier::BucketPathUnifier;
|
||||
use enums::by_table::voice_conversion_models::voice_conversion_model_type::VoiceConversionModelType;
|
||||
use errors::{anyhow, AnyhowResult};
|
||||
use filesys::file_deletion::safe_delete_directory::safe_delete_directory;
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ use std::time::Duration;
|
||||
|
||||
use log::{info, warn};
|
||||
|
||||
use buckets::public::weight_files::bucket_file_path::WeightFileBucketPath;
|
||||
use bucket_paths::legacy::typified_paths::public::weight_files::bucket_file_path::WeightFileBucketPath;
|
||||
use enums::by_table::model_weights::weights_types::WeightsType;
|
||||
use errors::AnyhowResult;
|
||||
use mysql_queries::queries::model_weights::get::get_weight::get_weight_by_token;
|
||||
|
||||
@@ -3,9 +3,15 @@ use actix_cors::Cors;
|
||||
use crate::util::netlify_branch_domain_matches::netlify_branch_domain_matches;
|
||||
|
||||
pub fn add_video_compositor(cors: Cors, _is_production: bool) -> Cors {
|
||||
cors.allowed_origin_fn(|origin, _req_head| {
|
||||
cors
|
||||
// Hypothetical domains
|
||||
.allowed_origin("https://memeboard.ai")
|
||||
.allowed_origin("https://dingboard.ai")
|
||||
// Netlify project
|
||||
.allowed_origin_fn(|origin, _req_head| {
|
||||
netlify_branch_domain_matches(origin, "storyteller-board.netlify.app")
|
||||
})
|
||||
// Local development
|
||||
.allowed_origin("http://localhost:5173/")
|
||||
}
|
||||
|
||||
@@ -17,6 +23,22 @@ mod tests {
|
||||
use crate::testing::assert_origin_invalid;
|
||||
use crate::testing::assert_origin_ok;
|
||||
|
||||
mod domains {
|
||||
use super::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn memeboard() {
|
||||
let production_cors = build_cors_config(ServerEnvironment::Production);
|
||||
assert_origin_ok(&production_cors, "https://memeboard.ai").await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn dingboard() {
|
||||
let production_cors = build_cors_config(ServerEnvironment::Production);
|
||||
assert_origin_ok(&production_cors, "https://dingboard.ai").await;
|
||||
}
|
||||
}
|
||||
|
||||
mod netlify {
|
||||
use super::*;
|
||||
|
||||
|
||||
@@ -16,18 +16,18 @@ path = "src/lib.rs"
|
||||
normal = ["my-workspace-hack"]
|
||||
|
||||
[dependencies]
|
||||
# Workspace hack (faster compile times)
|
||||
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
|
||||
|
||||
# Internal
|
||||
buckets = { path = "../../../_types/buckets" }
|
||||
bucket_paths = { path = "../../../lib/bucket_paths" }
|
||||
crockford = { path = "../../../lib/data/crockford" }
|
||||
errors = { path = "../../../lib/errors" }
|
||||
easyenv = { path = "../../../lib/easyenv" }
|
||||
errors = { path = "../../../lib/errors" }
|
||||
filesys = { path = "../../../lib/files/filesys" }
|
||||
hashing = { path = "../../../lib/files/hashing" }
|
||||
mimetypes = { path = "../../../lib/files/mimetypes" }
|
||||
storyteller_root = { path = "../../../lib/storyteller_root" }
|
||||
# Workspace hack (faster compile times)
|
||||
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
|
||||
|
||||
# External
|
||||
anyhow = "1.0.86"
|
||||
@@ -36,7 +36,7 @@ env_logger = "0.11.3"
|
||||
async-trait = "0.1.75"
|
||||
# The "never-encode-slash" feature seems to have been removed:
|
||||
# https://github.com/durch/rust-s3/pull/254
|
||||
rust-s3 = { version = "0.34.0" }
|
||||
rust-s3 = { version = "0.36.0-beta.2" }
|
||||
|
||||
# Runtime
|
||||
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
|
||||
|
||||
@@ -76,7 +76,7 @@ impl BucketClient {
|
||||
let optional_bucket_root = optional_bucket_root.map(|s| s.to_string());
|
||||
|
||||
Ok(Self {
|
||||
bucket,
|
||||
bucket: *bucket, // NB: We don't need to keep this boxed on the heap.
|
||||
optional_bucket_root,
|
||||
})
|
||||
}
|
||||
@@ -149,17 +149,6 @@ impl BucketClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// NB: New version has blocking client rather than blocking calls.
|
||||
// pub fn upload_file_blocking(&self, object_name: &str, bytes: &[u8]) -> anyhow::Result<()> {
|
||||
// info!("Filename for bucket: {}", object_name);
|
||||
//
|
||||
// let (_, code) = self.bucket.put_object_blocking(object_name, bytes)?;
|
||||
//
|
||||
// info!("upload code: {}", code);
|
||||
//
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
pub async fn upload_filename<P: AsRef<Path>, Q: AsRef<Path>>(
|
||||
&self,
|
||||
object_path: P,
|
||||
@@ -201,16 +190,6 @@ impl BucketClient {
|
||||
self.upload_file_with_content_type(&object_path_str, &buffer, content_type).await
|
||||
}
|
||||
|
||||
// NB: New version has blocking client rather than blocking calls.
|
||||
// pub fn upload_filename_blocking(&self, object_name: &str, filename: &Path) -> anyhow::Result<()> {
|
||||
// // TODO: does a newer version of this crate handle streaming/buffering file contents?
|
||||
// let mut file = File::open(filename)?;
|
||||
// let mut buffer : Vec<u8> = Vec::new();
|
||||
// file.read_to_end(&mut buffer)?;
|
||||
//
|
||||
// self.upload_file_blocking(object_name, &buffer)
|
||||
// }
|
||||
|
||||
pub async fn download_file(&self, path: &str) -> anyhow::Result<Vec<u8>> {
|
||||
info!("downloading from bucket: {}", path);
|
||||
|
||||
@@ -251,18 +230,4 @@ impl BucketClient {
|
||||
info!("download code: {}", status_code);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// NB: New version has blocking client rather than blocking calls.
|
||||
// pub fn download_file_blocking(&self, path: &str) -> anyhow::Result<Vec<u8>> {
|
||||
// info!("downloading from bucket: {}", path);
|
||||
// let (bytes, code) = self.bucket.get_object_blocking(path)?;
|
||||
//
|
||||
// match code {
|
||||
// 404 => bail!("File not found in bucket: {}", path),
|
||||
// _ => {},
|
||||
// }
|
||||
//
|
||||
// info!("download code: {}", code);
|
||||
// Ok(bytes)
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
pub mod bucket_client;
|
||||
pub mod bucket_path_unifier;
|
||||
pub mod remote_file_manager;
|
||||
pub mod remote_file_manager_mock;
|
||||
pub mod remote_file_manager;
|
||||
+7
-7
@@ -7,7 +7,9 @@ use errors::AnyhowResult;
|
||||
use crate::bucket_client::BucketClient;
|
||||
|
||||
#[async_trait]
|
||||
#[deprecated(note="this abstraction is too complicated, use another bucket client")]
|
||||
pub trait BucketOrchestrationCore {
|
||||
#[deprecated(note="this abstraction is too complicated, use another bucket client")]
|
||||
async fn download_file_to_disk(
|
||||
&self,
|
||||
object_path: String,
|
||||
@@ -15,6 +17,7 @@ pub trait BucketOrchestrationCore {
|
||||
is_public: bool,
|
||||
) -> AnyhowResult<()>;
|
||||
|
||||
#[deprecated(note="this abstraction is too complicated, use another bucket client")]
|
||||
async fn upload_file_with_content_type_process(&self, object_name: &str,
|
||||
bytes: &[u8],
|
||||
content_type: &str,
|
||||
@@ -22,6 +25,7 @@ pub trait BucketOrchestrationCore {
|
||||
}
|
||||
|
||||
|
||||
#[deprecated(note="this abstraction is too complicated, use another bucket client")]
|
||||
pub struct BucketOrchestration {
|
||||
access_key: String,
|
||||
secret_key: String,
|
||||
@@ -57,6 +61,7 @@ impl BucketOrchestrationCore for BucketOrchestration {
|
||||
}
|
||||
|
||||
impl BucketOrchestration {
|
||||
#[deprecated(note="this abstraction is too complicated, use another bucket client")]
|
||||
pub fn new_bucket_client_from_existing_env() -> AnyhowResult<Self> {
|
||||
let access_key = easyenv::get_env_string_required("ACCESS_KEY")?;
|
||||
let secret_key = easyenv::get_env_string_required("SECRET_KEY")?;
|
||||
@@ -82,6 +87,7 @@ impl BucketOrchestration {
|
||||
Ok(bucket_orchestration)
|
||||
}
|
||||
|
||||
#[deprecated(note="this abstraction is too complicated, use another bucket client")]
|
||||
pub fn new(
|
||||
access_key: String,
|
||||
secret_key: String,
|
||||
@@ -99,7 +105,7 @@ impl BucketOrchestration {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[deprecated(note="this abstraction is too complicated, use another bucket client")]
|
||||
async fn get_bucket_with_visibility(&self, public: bool) -> AnyhowResult<BucketClient> {
|
||||
let bucket_timeout = easyenv::get_env_duration_seconds_or_default(
|
||||
"BUCKET_TIMEOUT_SECONDS", Duration::from_secs(60 * 10));
|
||||
@@ -130,9 +136,3 @@ impl BucketOrchestration {
|
||||
Ok(bucket_client)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
pub fn test() {}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::remote_file_manager::remote_cloud_bucket_details::RemoteCloudBucketDetails;
|
||||
use bucket_paths::legacy::remote_file_manager_paths::remote_cloud_bucket_details::RemoteCloudBucketDetails;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FileMetaData {
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
pub mod remote_cloud_file_manager;
|
||||
pub mod remote_cloud_bucket_details;
|
||||
|
||||
pub mod bucket_orchestration;
|
||||
|
||||
|
||||
pub mod file_directory;
|
||||
pub mod file_meta_data;
|
||||
pub mod file_descriptor;
|
||||
|
||||
pub mod media_descriptor;
|
||||
pub mod weights_descriptor;
|
||||
pub mod remote_cloud_file_manager;
|
||||
|
||||
+18
-13
@@ -1,6 +1,8 @@
|
||||
use anyhow::anyhow;
|
||||
|
||||
use buckets::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use bucket_paths::legacy::remote_file_manager_paths::file_descriptor::FileDescriptor;
|
||||
use bucket_paths::legacy::remote_file_manager_paths::file_directory::FileBucketDirectory;
|
||||
use bucket_paths::legacy::remote_file_manager_paths::remote_cloud_bucket_details::RemoteCloudBucketDetails;
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use errors::AnyhowResult;
|
||||
use filesys::file_read_bytes::file_read_bytes;
|
||||
use filesys::file_size::file_size;
|
||||
@@ -8,18 +10,17 @@ use hashing::sha256::sha256_hash_file::sha256_hash_file;
|
||||
use mimetypes::mimetype_for_bytes::get_mimetype_for_bytes;
|
||||
|
||||
use crate::remote_file_manager::bucket_orchestration::{BucketOrchestration, BucketOrchestrationCore};
|
||||
use crate::remote_file_manager::file_directory::FileBucketDirectory;
|
||||
use crate::remote_file_manager::remote_cloud_bucket_details::RemoteCloudBucketDetails;
|
||||
|
||||
use super::file_descriptor::FileDescriptor;
|
||||
use super::file_meta_data::FileMetaData;
|
||||
|
||||
#[deprecated(note="this abstraction is too complicated, use another bucket client")]
|
||||
pub struct RemoteCloudFileClient {
|
||||
bucket_orchestration_client: Box<dyn BucketOrchestrationCore>
|
||||
}
|
||||
|
||||
impl RemoteCloudFileClient {
|
||||
|
||||
|
||||
#[deprecated(note="this abstraction is too complicated, use another bucket client")]
|
||||
pub async fn get_remote_cloud_file_client() -> AnyhowResult<Self> {
|
||||
let bucket_orchestration = match BucketOrchestration::new_bucket_client_from_existing_env() {
|
||||
Ok(client) => client,
|
||||
@@ -33,6 +34,7 @@ impl RemoteCloudFileClient {
|
||||
})
|
||||
}
|
||||
|
||||
#[deprecated(note="this abstraction is too complicated, use another bucket client")]
|
||||
pub fn new(bucket_orchestration_client: Box<dyn BucketOrchestrationCore>) -> Self {
|
||||
Self {
|
||||
bucket_orchestration_client
|
||||
@@ -40,6 +42,7 @@ impl RemoteCloudFileClient {
|
||||
}
|
||||
|
||||
// Where the to_system_file_path is let sd_checkpoint_path = work_temp_dir.path().join("sd_checkpoint.safetensors"); path and file name and extension
|
||||
#[deprecated(note="this abstraction is too complicated, use another bucket client")]
|
||||
pub async fn download_file(&self, remote_cloud_bucket_details:RemoteCloudBucketDetails, to_system_file_path:String) -> AnyhowResult<()> {
|
||||
let file_descriptor = remote_cloud_bucket_details.file_descriptor_from_bucket_details();
|
||||
let file_bucket_directory = FileBucketDirectory::from_existing_bucket_details(remote_cloud_bucket_details);
|
||||
@@ -50,6 +53,7 @@ impl RemoteCloudFileClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[deprecated(note="this abstraction is too complicated, use another bucket client")]
|
||||
pub async fn download_media_file(&self, media_file_path: &MediaFileBucketPath, to_system_file_path: String) -> AnyhowResult<()> {
|
||||
const IS_PUBLIC: bool = true; // NB: Media files are always public
|
||||
let full_remote_cloud_file_path = media_file_path.get_full_object_path_str().to_string();
|
||||
@@ -59,6 +63,7 @@ impl RemoteCloudFileClient {
|
||||
}
|
||||
|
||||
// return error or success with meta data.
|
||||
#[deprecated(note="this abstraction is too complicated, use another bucket client")]
|
||||
pub async fn upload_file(&self, file_descriptor:Box<dyn FileDescriptor>, from_system_file_path:&str) -> AnyhowResult<FileMetaData> {
|
||||
|
||||
// get file meta data
|
||||
@@ -94,6 +99,7 @@ impl RemoteCloudFileClient {
|
||||
}
|
||||
|
||||
// Retrieve the metadata from the file
|
||||
#[deprecated(note="this abstraction is too complicated, use another bucket client")]
|
||||
pub fn get_file_meta_data(system_file_path:&str) -> AnyhowResult<FileMetaData> {
|
||||
let file_size_bytes = file_size(system_file_path.clone())?;
|
||||
let sha256_checksum = sha256_hash_file(system_file_path.clone())?;
|
||||
@@ -112,15 +118,14 @@ impl RemoteCloudFileClient {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use async_trait::async_trait;
|
||||
use env_logger;
|
||||
use async_trait::async_trait;
|
||||
use env_logger;
|
||||
|
||||
use errors::AnyhowResult;
|
||||
use crate::remote_file_manager::bucket_orchestration::BucketOrchestrationCore;
|
||||
use bucket_paths::legacy::remote_file_manager_paths::weights_descriptor::WeightsLoRADescriptor;
|
||||
use errors::AnyhowResult;
|
||||
|
||||
use crate::remote_file_manager::bucket_orchestration::BucketOrchestrationCore;
|
||||
use crate::remote_file_manager::weights_descriptor::WeightsLoRADescriptor;
|
||||
|
||||
struct BucketOrchestrationMock {}
|
||||
struct BucketOrchestrationMock {}
|
||||
|
||||
#[async_trait]
|
||||
impl BucketOrchestrationCore for BucketOrchestrationMock {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
pub mod bucket_orchestration_mock;
|
||||
@@ -0,0 +1,41 @@
|
||||
[package]
|
||||
name = "google_sign_in"
|
||||
edition = "2018"
|
||||
version = "0.0.1"
|
||||
authors = [
|
||||
"Brandon Thomas <bt@brand.io>",
|
||||
"Brandon Thomas <echelon@gmail.com>",
|
||||
]
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
# NB: Public "google_sign_in" crate does not exist, so we're fine to use this internally.
|
||||
name = "google_sign_in"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
normal = ["my-workspace-hack"]
|
||||
|
||||
[dependencies]
|
||||
# Workspace hack (faster compile times)
|
||||
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
|
||||
|
||||
# Internal
|
||||
errors = { path = "../../errors" }
|
||||
|
||||
# External
|
||||
|
||||
# NB: jwt-simpe ordinarilly has a cmake dependency, "boring". Ignore that and use pure rust.
|
||||
jwt-simple = { version = "0.12.10" , default-features=false, features=["pure-rust"] }
|
||||
base64 = "0.13"
|
||||
|
||||
reqwest = "0.12.7"
|
||||
|
||||
# Serialization
|
||||
serde = "1.0.125"
|
||||
serde_derive = "1.0.125"
|
||||
serde_json = "1.0.108"
|
||||
|
||||
[dev-dependencies]
|
||||
testing = { path = "../../testing" }
|
||||
coarsetime = "0.1.34" # To set the clock for testing
|
||||
@@ -0,0 +1,25 @@
|
||||
use crate::certs::jwk_to_public_key::jwk_to_public_key;
|
||||
use crate::certs::key_map::KeyMap;
|
||||
use errors::{anyhow, AnyhowResult};
|
||||
|
||||
/// https://developers.google.com/identity/gsi/web/guides/verify-google-id-token
|
||||
/// "These keys are regularly rotated; examine the Cache-Control header in
|
||||
/// the response to determine when you should retrieve them again."
|
||||
const GOOGLE_CERTS_URL : &str = "https://www.googleapis.com/oauth2/v3/certs";
|
||||
|
||||
pub async fn download_cert_key_set() -> AnyhowResult<KeyMap> {
|
||||
let certs = download_certs().await?;
|
||||
let key_map = jwk_to_public_key(&certs)?;
|
||||
Ok(key_map)
|
||||
}
|
||||
|
||||
pub async fn download_certs() -> AnyhowResult<String> {
|
||||
let response = reqwest::get(GOOGLE_CERTS_URL).await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(anyhow!("Failed to download Google certs: {:?}", response.status()));
|
||||
}
|
||||
|
||||
let body = response.text().await?;
|
||||
Ok(body)
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
use crate::certs::key_map::KeyMap;
|
||||
use errors::{anyhow, AnyhowResult};
|
||||
use jwt_simple::prelude::RS256PublicKey;
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
/// Parse Google's JWK keys into usable public keys.
|
||||
/// Grab Google's keys from here: https://www.googleapis.com/oauth2/v3/certs
|
||||
/// These are updated frequently throughout the day and must be re-fetched.
|
||||
/// NB: This implementation is adapted from the crate `signin`.
|
||||
pub fn jwk_to_public_key(json_web_key: &str) -> AnyhowResult<KeyMap> {
|
||||
let mut key_map = KeyMap::new();
|
||||
|
||||
let json_keys: JsonKeys = serde_json::from_str(json_web_key)?;
|
||||
|
||||
for key in json_keys.keys {
|
||||
if key.use_ != "sig" {
|
||||
continue;
|
||||
}
|
||||
match key.alg.as_ref() {
|
||||
"RS256" => {
|
||||
let n_decoded = base64_decode_url(&key.n)?;
|
||||
let e_decoded = base64_decode_url(&key.e)?;
|
||||
|
||||
let public_key = RS256PublicKey::from_components(&n_decoded, &e_decoded)?
|
||||
.with_key_id(&key.kid);
|
||||
|
||||
key_map.insert(key.kid, public_key);
|
||||
}
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
if key_map.is_empty() {
|
||||
return Err(anyhow!("No keys found in JWK"));
|
||||
}
|
||||
|
||||
Ok(key_map)
|
||||
}
|
||||
|
||||
fn base64_decode_url(msg: &str) -> Result<Vec<u8>, base64::DecodeError> {
|
||||
base64::decode_config(msg, base64::URL_SAFE)
|
||||
}
|
||||
|
||||
// NB: These definitions are taken from the crate 'signin',
|
||||
// which has a correct implementation but does not expose
|
||||
// any of the payload for use. It also appears to be
|
||||
// unmaintained.
|
||||
#[derive(Deserialize)]
|
||||
pub struct JsonKeys {
|
||||
pub keys: Vec<JsonKey>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct JsonKey {
|
||||
pub kty: String,
|
||||
pub alg: String,
|
||||
#[serde(rename = "use")]
|
||||
pub use_: String,
|
||||
pub kid: String,
|
||||
pub n: String,
|
||||
pub e: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::certs::jwk_to_public_key::jwk_to_public_key;
|
||||
use jwt_simple::algorithms::RSAPublicKeyLike;
|
||||
use std::fs::read_to_string;
|
||||
use testing::test_file_path::test_file_path;
|
||||
|
||||
#[test]
|
||||
fn test_jwk_payload_decode() {
|
||||
let file = test_file_path("test_data/crypto/google_sign_in.2024-09-20.jwk").unwrap();
|
||||
let jwk_payload = read_to_string(file).unwrap();
|
||||
|
||||
let key_map = jwk_to_public_key(&jwk_payload).unwrap();
|
||||
|
||||
// Two keys in the payload
|
||||
assert_eq!(key_map.len(), 2);
|
||||
|
||||
// First key
|
||||
let public_key = key_map.get("b2620d5e7f132b52afe8875cdf3776c064249d04")
|
||||
.expect("key should exist");
|
||||
|
||||
assert_eq!(public_key.sha1_thumbprint(), "JI3Z88BipRcyp0WRF1KN9aX_P3Y");
|
||||
|
||||
// Second key
|
||||
let public_key = key_map.get("5aaff47c21d06e266cce395b2145c7c6d4730ea5")
|
||||
.expect("key should exist");
|
||||
|
||||
assert_eq!(public_key.sha1_thumbprint(), "vYxSlxKNLmgo1FWPtm2eVMbCnRU");
|
||||
|
||||
// Assertions for all
|
||||
for (key_id, public_key) in key_map.into_iter() {
|
||||
// Key id in map matches internally held
|
||||
let k_id = public_key.key_id().as_deref().unwrap();
|
||||
assert_eq!(key_id, k_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
use jwt_simple::algorithms::RS256PublicKey;
|
||||
|
||||
/// Map of JWK `kid` to `RS256PublicKey`.
|
||||
/// (JWK sets typically contain more than one key.)
|
||||
pub type KeyMap = HashMap<String, RS256PublicKey>;
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod download_certs;
|
||||
pub mod jwk_to_public_key;
|
||||
pub mod key_map;
|
||||
@@ -0,0 +1,49 @@
|
||||
use crate::claims::google_custom_claims::GoogleCustomClaims;
|
||||
use errors::{anyhow, AnyhowResult};
|
||||
use jwt_simple::claims::{Audiences, JWTClaims};
|
||||
|
||||
pub struct Claims {
|
||||
pub (crate) claims: JWTClaims<GoogleCustomClaims>,
|
||||
}
|
||||
|
||||
impl Claims {
|
||||
pub fn subject(&self) -> Option<&str> {
|
||||
self.claims.subject.as_deref()
|
||||
}
|
||||
|
||||
pub fn email(&self) -> Option<&str> {
|
||||
self.claims.custom.email.as_deref()
|
||||
}
|
||||
|
||||
pub fn email_verified(&self) -> bool {
|
||||
self.claims.custom.email_verified.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<&str> {
|
||||
self.claims.custom.name.as_deref()
|
||||
}
|
||||
|
||||
pub fn given_name(&self) -> Option<&str> {
|
||||
self.claims.custom.given_name.as_deref()
|
||||
}
|
||||
|
||||
pub fn family_name(&self) -> Option<&str> {
|
||||
self.claims.custom.family_name.as_deref()
|
||||
}
|
||||
|
||||
pub fn locale(&self) -> Option<&str> {
|
||||
self.claims.custom.locale.as_deref()
|
||||
}
|
||||
|
||||
/// Determine if the claim audience is as expected.
|
||||
/// This is necessary so third parties don't send claims signed on their behalf by Google.
|
||||
pub fn audience_matches(&self, audience: &str) -> AnyhowResult<bool> {
|
||||
match self.claims.audiences.as_ref() {
|
||||
Some(Audiences::AsString(claim_audience)) => Ok(claim_audience.eq(audience)),
|
||||
Some(Audiences::AsSet(audiences)) => Ok(audiences.contains(audience)),
|
||||
_ => Err(anyhow!("Invalid audience type")),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(bt,2024-09-20): Other fields if important.
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
/// Extra Payload of Google Sign In JWT
|
||||
/// https://developers.google.com/identity/openid-connect/openid-connect
|
||||
/// https://stackoverflow.com/questions/31056412/what-all-these-fields-mean
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct GoogleCustomClaims {
|
||||
/// The user's email address. Provided only if you included the email scope in your
|
||||
/// request. The value of this claim may not be unique to this account and could
|
||||
/// change over time, therefore you should not use this value as the primary identifier
|
||||
/// to link to your user record. You also can't rely on the domain of the email claim
|
||||
/// to identify users of Google Workspace or Cloud organizations; use the hd claim instead.
|
||||
pub email: Option<String>,
|
||||
|
||||
/// True if the user's e-mail address has been verified; otherwise false.
|
||||
pub email_verified: Option<bool>,
|
||||
|
||||
/// The user's full name, in a displayable form. (See Google documentation)
|
||||
pub name: Option<String>,
|
||||
|
||||
/// The user's given name(s) or first name(s). Might be provided when a name claim is present.
|
||||
pub given_name: Option<String>,
|
||||
|
||||
/// The user's surname(s) or last name(s). Might be provided when a name claim is present.
|
||||
pub family_name: Option<String>,
|
||||
|
||||
/// The client_id of the authorized presenter. This claim is only needed when the party
|
||||
/// requesting the ID token is not the same as the audience of the ID token. This may be
|
||||
/// the case at Google for hybrid apps where a web application and Android app have a
|
||||
/// different OAuth 2.0 client_id but share the same Google APIs project.
|
||||
pub azp: Option<String>,
|
||||
|
||||
/// An identifier for the user, unique among all Google accounts and never reused.
|
||||
/// A Google account can have multiple email addresses at different points in time,
|
||||
/// but the sub value is never changed. Use sub within your application as the
|
||||
/// unique-identifier key for the user. Maximum length of 255 case-sensitive ASCII
|
||||
/// characters.
|
||||
pub sub: Option<String>,
|
||||
|
||||
/// The URL of the user's profile picture. Might be provided when:
|
||||
/// - The request scope included the string "profile"
|
||||
/// - The ID token is returned from a token refresh
|
||||
/// When picture claims are present, you can use them to update your app's user records.
|
||||
/// Note that this claim is never guaranteed to be present.
|
||||
pub picture: Option<String>,
|
||||
|
||||
/// The user's locale, represented by a BCP 47 language tag.
|
||||
/// Might be provided when a name claim is present.
|
||||
pub locale: Option<String>,
|
||||
|
||||
// Other fields are pulled out of the custom payload:
|
||||
|
||||
// /// The audience that this ID token is intended for. It must be one of the
|
||||
// /// OAuth 2.0 client IDs of your application.
|
||||
// /// NB: You must check that this matches the audience (the app client id)
|
||||
// pub aud: Option<String>,
|
||||
|
||||
// /// The Issuer Identifier for the Issuer of the response.
|
||||
// /// Always `https://accounts.google.com` or `accounts.google.com` for Google ID tokens.
|
||||
// /// NB: You must check that this matches the issuer.
|
||||
// pub iss: Option<String>,
|
||||
|
||||
// pub nbf: Option<String>,
|
||||
// pub jti: Option<String>,
|
||||
// pub iat: Option<String>,
|
||||
// pub exp: Option<String>,
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod claims;
|
||||
pub mod google_custom_claims;
|
||||
@@ -0,0 +1,275 @@
|
||||
use crate::certs::key_map::KeyMap;
|
||||
use crate::claims::claims::Claims;
|
||||
use crate::claims::google_custom_claims::GoogleCustomClaims;
|
||||
use crate::errors::VerifyError;
|
||||
use crate::jwt::decode_jwt_header::decode_jwt_header;
|
||||
use errors::anyhow;
|
||||
use jwt_simple::algorithms::RS256PublicKey;
|
||||
use jwt_simple::algorithms::RSAPublicKeyLike;
|
||||
use jwt_simple::common::VerificationOptions;
|
||||
use jwt_simple::JWTError;
|
||||
use std::collections::HashSet;
|
||||
|
||||
/// Decode a Google Sign In JWT.
|
||||
/// Verification options can be supplied to increase clock skew tolerance, etc.
|
||||
pub fn decode_and_verify_token_claims(keys: &KeyMap, token: &str, mut options: Option<VerificationOptions>) -> Result<Claims, VerifyError> {
|
||||
let header = decode_jwt_header(token)
|
||||
.map_err(|err| VerifyError::JwtDecodeError { source: err })?;
|
||||
|
||||
let key_id = header.kid.as_deref()
|
||||
.or_else(|| keys.keys().next().map(|k|k.as_str()))
|
||||
.ok_or_else(|| anyhow!("No key ID found"))?;
|
||||
|
||||
let key = keys.get(key_id)
|
||||
.ok_or_else(|| VerifyError::JwtKeyMissing { requested_key: key_id.to_string() })?;
|
||||
|
||||
if options.is_none() {
|
||||
options = Some(VerificationOptions {
|
||||
allowed_issuers: Some(HashSet::from([
|
||||
"https://accounts.google.com".to_string(),
|
||||
"accounts.google.com".to_string(),
|
||||
])),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
decode_and_verify_token_claims_with_key(key, token, options)
|
||||
}
|
||||
|
||||
pub fn decode_and_verify_token_claims_with_key(key: &RS256PublicKey, token: &str, options: Option<VerificationOptions>) -> Result<Claims, VerifyError> {
|
||||
let result = key.verify_token::<GoogleCustomClaims>(token, options);
|
||||
|
||||
let claims = match result {
|
||||
Ok(claims) => claims,
|
||||
Err(err) => {
|
||||
if let Some(inner_error) = err.downcast_ref::<JWTError>() {
|
||||
match inner_error {
|
||||
JWTError::RequiredAudienceMismatch => return Err(VerifyError::JwtInvalidAudience),
|
||||
JWTError::RequiredAudienceMissing => return Err(VerifyError::JwtInvalidAudience),
|
||||
JWTError::RequiredIssuerMismatch => return Err(VerifyError::JwtInvalidIssuer),
|
||||
JWTError::RequiredIssuerMissing => return Err(VerifyError::JwtInvalidIssuer),
|
||||
JWTError::TokenHasExpired => return Err(VerifyError::JwtExpired),
|
||||
_ => {}, // Fall-through
|
||||
}
|
||||
}
|
||||
return Err(VerifyError::AnyhowError(err));
|
||||
}
|
||||
};
|
||||
|
||||
match claims.issuer.as_deref() {
|
||||
Some("https://accounts.google.com" | "accounts.google.com") => {} // Permitted
|
||||
_ => {
|
||||
return Err(VerifyError::JwtInvalidIssuer);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Claims {
|
||||
claims,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::certs::jwk_to_public_key::jwk_to_public_key;
|
||||
use crate::decode_and_verify_token_claims::decode_and_verify_token_claims;
|
||||
use crate::errors::VerifyError;
|
||||
use coarsetime::Duration;
|
||||
use jwt_simple::claims::Audiences;
|
||||
use jwt_simple::prelude::VerificationOptions;
|
||||
use std::collections::HashSet;
|
||||
use std::fs::read_to_string;
|
||||
use testing::test_file_path::test_file_path;
|
||||
/*
|
||||
Example payload:
|
||||
iss https://accounts.google.com
|
||||
azp 788843034237-uqcg8tbgofrcf1to37e1bqphd924jaf6.apps.googleusercontent.com
|
||||
aud 788843034237-uqcg8tbgofrcf1to37e1bqphd924jaf6.apps.googleusercontent.com
|
||||
sub 113101967612396793777
|
||||
email vocodes2020@gmail.com
|
||||
email_verified true
|
||||
nbf 1726786100
|
||||
name Vocodes Vocodes
|
||||
picture https://lh3.googleusercontent.com/a/ACg8ocLz2-2OaAm0MQxR6j8CNr-Po8_Xr-aryATiCn4c0i_TuDmL_g=s96-c
|
||||
given_name Vocodes
|
||||
family_name Vocodes
|
||||
iat 1726786400
|
||||
exp 1726790000
|
||||
jti 4d44eeac06ce79fc0ab2270cfeea30d8acf77613
|
||||
*/
|
||||
|
||||
#[test]
|
||||
fn test_decode_success_path() {
|
||||
let jwk_file = test_file_path("test_data/crypto/google_sign_in.2024-09-20.jwk").unwrap();
|
||||
let jwk_payload = read_to_string(jwk_file).unwrap();
|
||||
let key_map = jwk_to_public_key(&jwk_payload).unwrap();
|
||||
|
||||
//let credential = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImIyNjIwZDVlN2YxMzJiNTJhZmU4ODc1Y2RmMzc3NmMwNjQyNDlkMDQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI3ODg4NDMwMzQyMzctdXFjZzh0YmdvZnJjZjF0bzM3ZTFicXBoZDkyNGphZjYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI3ODg4NDMwMzQyMzctdXFjZzh0YmdvZnJjZjF0bzM3ZTFicXBoZDkyNGphZjYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTMxMDE5Njc2MTIzOTY3OTM3NzciLCJlbWFpbCI6InZvY29kZXMyMDIwQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYmYiOjE3MjY4Njk0OTgsIm5hbWUiOiJWb2NvZGVzIFZvY29kZXMiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EvQUNnOG9jTHoyLTJPYUFtME1ReFI2ajhDTnItUG84X1hyLWFyeUFUaUNuNGMwaV9UdURtTF9nPXM5Ni1jIiwiZ2l2ZW5fbmFtZSI6IlZvY29kZXMiLCJmYW1pbHlfbmFtZSI6IlZvY29kZXMiLCJpYXQiOjE3MjY4Njk3OTgsImV4cCI6MTcyNjg3MzM5OCwianRpIjoiMGUyZTI0ZjE0M2ZlODk1NDlkM2I0NTkxNjBmYTY0ODc0YzFkZmIyYiJ9.ciKy6sIbZx1Z4mtXdXwy6eVDW2Q96ed7MyP1jOdGow5p9FpDUR8lTqIdMiKEkrp3j99Ipjv2y7iMZH23pC5ipWKqkP_Z4yxotN3ULnKFqkFnyEsHtvQDfc5CSmICcREEwjf2n9ngGycxYZ342n9rKRr893TFzp5sjy2DHxv1zpt6hsA2NSjoKPA4fmBOdJ_GnyWZYMQ1ABVFFnCsEVME2C53DBAWNs3OSlDN4U-QCRlb0LtpxU9_f1GUN4Vff7aSuIyGKY9SCIWlbf772br4HEjLXCDygaTRTgBUa-f5XJaOKLnsORCQwki7EqHgGYemMoQCOVaFXHj7N_8xBxyDgw";
|
||||
let credential = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImIyNjIwZDVlN2YxMzJiNTJhZmU4ODc1Y2RmMzc3NmMwNjQyNDlkMDQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI3ODg4NDMwMzQyMzctdXFjZzh0YmdvZnJjZjF0bzM3ZTFicXBoZDkyNGphZjYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI3ODg4NDMwMzQyMzctdXFjZzh0YmdvZnJjZjF0bzM3ZTFicXBoZDkyNGphZjYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTMxMDE5Njc2MTIzOTY3OTM3NzciLCJlbWFpbCI6InZvY29kZXMyMDIwQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYmYiOjE3MjY3ODYxMDAsIm5hbWUiOiJWb2NvZGVzIFZvY29kZXMiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EvQUNnOG9jTHoyLTJPYUFtME1ReFI2ajhDTnItUG84X1hyLWFyeUFUaUNuNGMwaV9UdURtTF9nPXM5Ni1jIiwiZ2l2ZW5fbmFtZSI6IlZvY29kZXMiLCJmYW1pbHlfbmFtZSI6IlZvY29kZXMiLCJpYXQiOjE3MjY3ODY0MDAsImV4cCI6MTcyNjc5MDAwMCwianRpIjoiNGQ0NGVlYWMwNmNlNzlmYzBhYjIyNzBjZmVlYTMwZDhhY2Y3NzYxMyJ9.EYg71yIkvhxFGc8ZVCXeTOAmPAtLYDphHnkdf1sh8b_Jz4Y7S1DpmiTqQ1ytxu7J1xNixvdwhuIDzSlCvlxaFl8475GvAlyPTNtZtmWbFD5SRM_XHLOynijOp8WQ4nej-CHvT1KjjqMfkZ1EeQMoWk1H72PxPg_RiUgzsklkUs1wOkLAySk7R3EIAl7bIzpoY_WH2pxv9ccFpBtKDHaDqHkxAWBUQX0-G7ZXZBPVz07V28ZfdbzFDapjZaUFbumazh_-J2-9AA6JkcteF4h_gpbBcLYAuxt5bWI5FECWbYe42khwb93WJ5SK12Tt0EPoyzIObJs14NWGAajtHTg3wA";
|
||||
|
||||
let options = VerificationOptions {
|
||||
time_tolerance: Some(Duration::from_days(365 * 30)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// As should this much more ergonomic method
|
||||
let claims = decode_and_verify_token_claims(&key_map, credential, Some(options)).unwrap();
|
||||
|
||||
// Custom fields
|
||||
assert_eq!(claims.claims.custom.email, Some("vocodes2020@gmail.com".to_string()));
|
||||
assert_eq!(claims.claims.custom.email_verified, Some(true));
|
||||
assert_eq!(claims.claims.custom.azp, Some("788843034237-uqcg8tbgofrcf1to37e1bqphd924jaf6.apps.googleusercontent.com".to_string()));
|
||||
assert_eq!(claims.claims.custom.name, Some("Vocodes Vocodes".to_string()));
|
||||
assert_eq!(claims.claims.custom.picture, Some("https://lh3.googleusercontent.com/a/ACg8ocLz2-2OaAm0MQxR6j8CNr-Po8_Xr-aryATiCn4c0i_TuDmL_g=s96-c".to_string()));
|
||||
assert_eq!(claims.claims.custom.given_name, Some("Vocodes".to_string()));
|
||||
assert_eq!(claims.claims.custom.family_name, Some("Vocodes".to_string()));
|
||||
|
||||
// Standard fields
|
||||
assert_eq!(claims.claims.jwt_id, Some("4d44eeac06ce79fc0ab2270cfeea30d8acf77613".to_string()));
|
||||
assert_eq!(claims.claims.issuer, Some("https://accounts.google.com".to_string()));
|
||||
assert_eq!(claims.claims.subject, Some("113101967612396793777".to_string()));
|
||||
assert_eq!(claims.claims.issued_at.unwrap().as_secs(), 1726786400);
|
||||
|
||||
// Methods
|
||||
assert_eq!(claims.email_verified(), true);
|
||||
assert_eq!(claims.email(), Some("vocodes2020@gmail.com"));
|
||||
assert_eq!(claims.audience_matches("788843034237-uqcg8tbgofrcf1to37e1bqphd924jaf6.apps.googleusercontent.com").unwrap(), true);
|
||||
|
||||
match claims.claims.audiences.unwrap() {
|
||||
Audiences::AsString(audience) => assert_eq!(audience, "788843034237-uqcg8tbgofrcf1to37e1bqphd924jaf6.apps.googleusercontent.com"),
|
||||
Audiences::AsSet(_audiences) => panic!("Expected a single audience"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_success_path_with_expectations() {
|
||||
let jwk_file = test_file_path("test_data/crypto/google_sign_in.2024-09-20.jwk").unwrap();
|
||||
let jwk_payload = read_to_string(jwk_file).unwrap();
|
||||
let key_map = jwk_to_public_key(&jwk_payload).unwrap();
|
||||
|
||||
let credential = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImIyNjIwZDVlN2YxMzJiNTJhZmU4ODc1Y2RmMzc3NmMwNjQyNDlkMDQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI3ODg4NDMwMzQyMzctdXFjZzh0YmdvZnJjZjF0bzM3ZTFicXBoZDkyNGphZjYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI3ODg4NDMwMzQyMzctdXFjZzh0YmdvZnJjZjF0bzM3ZTFicXBoZDkyNGphZjYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTMxMDE5Njc2MTIzOTY3OTM3NzciLCJlbWFpbCI6InZvY29kZXMyMDIwQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYmYiOjE3MjY3ODYxMDAsIm5hbWUiOiJWb2NvZGVzIFZvY29kZXMiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EvQUNnOG9jTHoyLTJPYUFtME1ReFI2ajhDTnItUG84X1hyLWFyeUFUaUNuNGMwaV9UdURtTF9nPXM5Ni1jIiwiZ2l2ZW5fbmFtZSI6IlZvY29kZXMiLCJmYW1pbHlfbmFtZSI6IlZvY29kZXMiLCJpYXQiOjE3MjY3ODY0MDAsImV4cCI6MTcyNjc5MDAwMCwianRpIjoiNGQ0NGVlYWMwNmNlNzlmYzBhYjIyNzBjZmVlYTMwZDhhY2Y3NzYxMyJ9.EYg71yIkvhxFGc8ZVCXeTOAmPAtLYDphHnkdf1sh8b_Jz4Y7S1DpmiTqQ1ytxu7J1xNixvdwhuIDzSlCvlxaFl8475GvAlyPTNtZtmWbFD5SRM_XHLOynijOp8WQ4nej-CHvT1KjjqMfkZ1EeQMoWk1H72PxPg_RiUgzsklkUs1wOkLAySk7R3EIAl7bIzpoY_WH2pxv9ccFpBtKDHaDqHkxAWBUQX0-G7ZXZBPVz07V28ZfdbzFDapjZaUFbumazh_-J2-9AA6JkcteF4h_gpbBcLYAuxt5bWI5FECWbYe42khwb93WJ5SK12Tt0EPoyzIObJs14NWGAajtHTg3wA";
|
||||
|
||||
let options = VerificationOptions {
|
||||
time_tolerance: Some(Duration::from_days(365 * 30)),
|
||||
allowed_audiences: Some(HashSet::from([
|
||||
"788843034237-uqcg8tbgofrcf1to37e1bqphd924jaf6.apps.googleusercontent.com".to_string(),
|
||||
])),
|
||||
allowed_issuers: Some(HashSet::from([
|
||||
"https://accounts.google.com".to_string(),
|
||||
"accounts.google.com".to_string(),
|
||||
])),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let claims = decode_and_verify_token_claims(&key_map, credential, Some(options)).unwrap();
|
||||
|
||||
assert_eq!(claims.claims.issuer, Some("https://accounts.google.com".to_string()));
|
||||
assert_eq!(claims.audience_matches("788843034237-uqcg8tbgofrcf1to37e1bqphd924jaf6.apps.googleusercontent.com").unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_jwk_keys() {
|
||||
let jwk_file = test_file_path("test_data/crypto/google_sign_in.2024-04-01.jwk").unwrap();
|
||||
let jwk_payload = read_to_string(jwk_file).unwrap();
|
||||
let key_map = jwk_to_public_key(&jwk_payload).unwrap();
|
||||
|
||||
let credential = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImIyNjIwZDVlN2YxMzJiNTJhZmU4ODc1Y2RmMzc3NmMwNjQyNDlkMDQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI3ODg4NDMwMzQyMzctdXFjZzh0YmdvZnJjZjF0bzM3ZTFicXBoZDkyNGphZjYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI3ODg4NDMwMzQyMzctdXFjZzh0YmdvZnJjZjF0bzM3ZTFicXBoZDkyNGphZjYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTMxMDE5Njc2MTIzOTY3OTM3NzciLCJlbWFpbCI6InZvY29kZXMyMDIwQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYmYiOjE3MjY3ODYxMDAsIm5hbWUiOiJWb2NvZGVzIFZvY29kZXMiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EvQUNnOG9jTHoyLTJPYUFtME1ReFI2ajhDTnItUG84X1hyLWFyeUFUaUNuNGMwaV9UdURtTF9nPXM5Ni1jIiwiZ2l2ZW5fbmFtZSI6IlZvY29kZXMiLCJmYW1pbHlfbmFtZSI6IlZvY29kZXMiLCJpYXQiOjE3MjY3ODY0MDAsImV4cCI6MTcyNjc5MDAwMCwianRpIjoiNGQ0NGVlYWMwNmNlNzlmYzBhYjIyNzBjZmVlYTMwZDhhY2Y3NzYxMyJ9.EYg71yIkvhxFGc8ZVCXeTOAmPAtLYDphHnkdf1sh8b_Jz4Y7S1DpmiTqQ1ytxu7J1xNixvdwhuIDzSlCvlxaFl8475GvAlyPTNtZtmWbFD5SRM_XHLOynijOp8WQ4nej-CHvT1KjjqMfkZ1EeQMoWk1H72PxPg_RiUgzsklkUs1wOkLAySk7R3EIAl7bIzpoY_WH2pxv9ccFpBtKDHaDqHkxAWBUQX0-G7ZXZBPVz07V28ZfdbzFDapjZaUFbumazh_-J2-9AA6JkcteF4h_gpbBcLYAuxt5bWI5FECWbYe42khwb93WJ5SK12Tt0EPoyzIObJs14NWGAajtHTg3wA";
|
||||
|
||||
let options = VerificationOptions {
|
||||
time_tolerance: Some(Duration::from_days(365 * 30)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match decode_and_verify_token_claims(&key_map, credential, Some(options)) {
|
||||
Err(VerifyError::JwtKeyMissing { requested_key}) => {
|
||||
assert_eq!(requested_key, "b2620d5e7f132b52afe8875cdf3776c064249d04");
|
||||
}
|
||||
Err(err) => {
|
||||
panic!("Unexpected error: {:?}", err);
|
||||
}
|
||||
Ok(value) => {
|
||||
panic!("Unexpected success: {:?}", value.claims);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expired_jwt_token() {
|
||||
let jwk_file = test_file_path("test_data/crypto/google_sign_in.2024-09-20.jwk").unwrap();
|
||||
let jwk_payload = read_to_string(jwk_file).unwrap();
|
||||
let key_map = jwk_to_public_key(&jwk_payload).unwrap();
|
||||
|
||||
let credential = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImIyNjIwZDVlN2YxMzJiNTJhZmU4ODc1Y2RmMzc3NmMwNjQyNDlkMDQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI3ODg4NDMwMzQyMzctdXFjZzh0YmdvZnJjZjF0bzM3ZTFicXBoZDkyNGphZjYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI3ODg4NDMwMzQyMzctdXFjZzh0YmdvZnJjZjF0bzM3ZTFicXBoZDkyNGphZjYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTMxMDE5Njc2MTIzOTY3OTM3NzciLCJlbWFpbCI6InZvY29kZXMyMDIwQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYmYiOjE3MjY3ODYxMDAsIm5hbWUiOiJWb2NvZGVzIFZvY29kZXMiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EvQUNnOG9jTHoyLTJPYUFtME1ReFI2ajhDTnItUG84X1hyLWFyeUFUaUNuNGMwaV9UdURtTF9nPXM5Ni1jIiwiZ2l2ZW5fbmFtZSI6IlZvY29kZXMiLCJmYW1pbHlfbmFtZSI6IlZvY29kZXMiLCJpYXQiOjE3MjY3ODY0MDAsImV4cCI6MTcyNjc5MDAwMCwianRpIjoiNGQ0NGVlYWMwNmNlNzlmYzBhYjIyNzBjZmVlYTMwZDhhY2Y3NzYxMyJ9.EYg71yIkvhxFGc8ZVCXeTOAmPAtLYDphHnkdf1sh8b_Jz4Y7S1DpmiTqQ1ytxu7J1xNixvdwhuIDzSlCvlxaFl8475GvAlyPTNtZtmWbFD5SRM_XHLOynijOp8WQ4nej-CHvT1KjjqMfkZ1EeQMoWk1H72PxPg_RiUgzsklkUs1wOkLAySk7R3EIAl7bIzpoY_WH2pxv9ccFpBtKDHaDqHkxAWBUQX0-G7ZXZBPVz07V28ZfdbzFDapjZaUFbumazh_-J2-9AA6JkcteF4h_gpbBcLYAuxt5bWI5FECWbYe42khwb93WJ5SK12Tt0EPoyzIObJs14NWGAajtHTg3wA";
|
||||
|
||||
match decode_and_verify_token_claims(&key_map, credential, None) {
|
||||
Err(VerifyError::JwtExpired) => {
|
||||
// Expected error case
|
||||
}
|
||||
Err(err) => {
|
||||
panic!("Unexpected error: {:?}", err);
|
||||
}
|
||||
Ok(value) => {
|
||||
panic!("Unexpected success: {:?}", value.claims);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_issuer() {
|
||||
let jwk_file = test_file_path("test_data/crypto/google_sign_in.2024-09-20.jwk").unwrap();
|
||||
let jwk_payload = read_to_string(jwk_file).unwrap();
|
||||
let key_map = jwk_to_public_key(&jwk_payload).unwrap();
|
||||
|
||||
let credential = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImIyNjIwZDVlN2YxMzJiNTJhZmU4ODc1Y2RmMzc3NmMwNjQyNDlkMDQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI3ODg4NDMwMzQyMzctdXFjZzh0YmdvZnJjZjF0bzM3ZTFicXBoZDkyNGphZjYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI3ODg4NDMwMzQyMzctdXFjZzh0YmdvZnJjZjF0bzM3ZTFicXBoZDkyNGphZjYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTMxMDE5Njc2MTIzOTY3OTM3NzciLCJlbWFpbCI6InZvY29kZXMyMDIwQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYmYiOjE3MjY3ODYxMDAsIm5hbWUiOiJWb2NvZGVzIFZvY29kZXMiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EvQUNnOG9jTHoyLTJPYUFtME1ReFI2ajhDTnItUG84X1hyLWFyeUFUaUNuNGMwaV9UdURtTF9nPXM5Ni1jIiwiZ2l2ZW5fbmFtZSI6IlZvY29kZXMiLCJmYW1pbHlfbmFtZSI6IlZvY29kZXMiLCJpYXQiOjE3MjY3ODY0MDAsImV4cCI6MTcyNjc5MDAwMCwianRpIjoiNGQ0NGVlYWMwNmNlNzlmYzBhYjIyNzBjZmVlYTMwZDhhY2Y3NzYxMyJ9.EYg71yIkvhxFGc8ZVCXeTOAmPAtLYDphHnkdf1sh8b_Jz4Y7S1DpmiTqQ1ytxu7J1xNixvdwhuIDzSlCvlxaFl8475GvAlyPTNtZtmWbFD5SRM_XHLOynijOp8WQ4nej-CHvT1KjjqMfkZ1EeQMoWk1H72PxPg_RiUgzsklkUs1wOkLAySk7R3EIAl7bIzpoY_WH2pxv9ccFpBtKDHaDqHkxAWBUQX0-G7ZXZBPVz07V28ZfdbzFDapjZaUFbumazh_-J2-9AA6JkcteF4h_gpbBcLYAuxt5bWI5FECWbYe42khwb93WJ5SK12Tt0EPoyzIObJs14NWGAajtHTg3wA";
|
||||
|
||||
let options = VerificationOptions {
|
||||
time_tolerance: Some(Duration::from_days(365 * 30)),
|
||||
allowed_issuers: Some(HashSet::from([
|
||||
"ISSUER_1".to_string(),
|
||||
"ISSUER_2".to_string(),
|
||||
])),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match decode_and_verify_token_claims(&key_map, credential, Some(options)) {
|
||||
Err(VerifyError::JwtInvalidIssuer) => {
|
||||
// Expected error case
|
||||
}
|
||||
Err(err) => {
|
||||
panic!("Unexpected error: {:?}", err);
|
||||
}
|
||||
Ok(value) => {
|
||||
panic!("Unexpected success: {:?}", value.claims);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_audience() {
|
||||
let jwk_file = test_file_path("test_data/crypto/google_sign_in.2024-09-20.jwk").unwrap();
|
||||
let jwk_payload = read_to_string(jwk_file).unwrap();
|
||||
let key_map = jwk_to_public_key(&jwk_payload).unwrap();
|
||||
|
||||
let credential = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImIyNjIwZDVlN2YxMzJiNTJhZmU4ODc1Y2RmMzc3NmMwNjQyNDlkMDQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI3ODg4NDMwMzQyMzctdXFjZzh0YmdvZnJjZjF0bzM3ZTFicXBoZDkyNGphZjYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI3ODg4NDMwMzQyMzctdXFjZzh0YmdvZnJjZjF0bzM3ZTFicXBoZDkyNGphZjYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTMxMDE5Njc2MTIzOTY3OTM3NzciLCJlbWFpbCI6InZvY29kZXMyMDIwQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYmYiOjE3MjY3ODYxMDAsIm5hbWUiOiJWb2NvZGVzIFZvY29kZXMiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EvQUNnOG9jTHoyLTJPYUFtME1ReFI2ajhDTnItUG84X1hyLWFyeUFUaUNuNGMwaV9UdURtTF9nPXM5Ni1jIiwiZ2l2ZW5fbmFtZSI6IlZvY29kZXMiLCJmYW1pbHlfbmFtZSI6IlZvY29kZXMiLCJpYXQiOjE3MjY3ODY0MDAsImV4cCI6MTcyNjc5MDAwMCwianRpIjoiNGQ0NGVlYWMwNmNlNzlmYzBhYjIyNzBjZmVlYTMwZDhhY2Y3NzYxMyJ9.EYg71yIkvhxFGc8ZVCXeTOAmPAtLYDphHnkdf1sh8b_Jz4Y7S1DpmiTqQ1ytxu7J1xNixvdwhuIDzSlCvlxaFl8475GvAlyPTNtZtmWbFD5SRM_XHLOynijOp8WQ4nej-CHvT1KjjqMfkZ1EeQMoWk1H72PxPg_RiUgzsklkUs1wOkLAySk7R3EIAl7bIzpoY_WH2pxv9ccFpBtKDHaDqHkxAWBUQX0-G7ZXZBPVz07V28ZfdbzFDapjZaUFbumazh_-J2-9AA6JkcteF4h_gpbBcLYAuxt5bWI5FECWbYe42khwb93WJ5SK12Tt0EPoyzIObJs14NWGAajtHTg3wA";
|
||||
|
||||
let options = VerificationOptions {
|
||||
time_tolerance: Some(Duration::from_days(365 * 30)),
|
||||
allowed_audiences: Some(HashSet::from([
|
||||
"AUDIENCE_1".to_string(),
|
||||
"AUDIENCE_2".to_string(),
|
||||
])),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match decode_and_verify_token_claims(&key_map, credential, Some(options)) {
|
||||
Err(VerifyError::JwtInvalidAudience) => {
|
||||
// Expected error case
|
||||
}
|
||||
Err(err) => {
|
||||
panic!("Unexpected error: {:?}", err);
|
||||
}
|
||||
Ok(value) => {
|
||||
panic!("Unexpected success: {:?}", value.claims);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
use errors::AnyhowError;
|
||||
use std::error::Error;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VerifyError {
|
||||
/// Could not decode a JWT token.
|
||||
JwtDecodeError { source: AnyhowError },
|
||||
|
||||
/// The JWT has expired (was issued before the current
|
||||
/// clock + deadline + skew tolerance)
|
||||
JwtExpired,
|
||||
|
||||
/// The key requested by the JWT was not available.
|
||||
/// This likely means we need to refresh our JWK key set.
|
||||
JwtKeyMissing { requested_key: String },
|
||||
|
||||
/// The audience for the JWT claims was absent or unexpected.
|
||||
JwtInvalidAudience,
|
||||
|
||||
/// The issuer of the JWT claims was absent or unexpected.
|
||||
JwtInvalidIssuer,
|
||||
|
||||
/// Any other error type we haven't wrapped.
|
||||
AnyhowError(AnyhowError),
|
||||
}
|
||||
|
||||
impl Error for VerifyError {}
|
||||
|
||||
impl Display for VerifyError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::JwtDecodeError { source } => {
|
||||
write!(f, "JWT decode error: {}", source)
|
||||
}
|
||||
Self::JwtExpired => {
|
||||
write!(f, "JWT token has expired")
|
||||
}
|
||||
Self::JwtKeyMissing { requested_key } => {
|
||||
write!(f, "JWT requested key absent from JWK: {}", requested_key)
|
||||
}
|
||||
Self::JwtInvalidAudience => {
|
||||
write!(f, "Unexpected or missing JWT audience")
|
||||
}
|
||||
Self::JwtInvalidIssuer => {
|
||||
write!(f, "Unexpected or missing JWT issuer")
|
||||
}
|
||||
Self::AnyhowError(err) => {
|
||||
write!(f, "AnyhowError: {}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AnyhowError> for VerifyError {
|
||||
fn from(value: AnyhowError) -> Self {
|
||||
Self::AnyhowError(value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
use errors::{anyhow, AnyhowResult};
|
||||
use jwt_simple::prelude::Base64UrlSafeNoPadding;
|
||||
use jwt_simple::reexports::ct_codecs::Decoder;
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct JwtHeader {
|
||||
/// The key identifier to use to verify the JWT signature
|
||||
pub(crate) kid: Option<String>,
|
||||
// Other fields absent
|
||||
}
|
||||
|
||||
pub fn decode_jwt_header(jwt: &str) -> AnyhowResult<JwtHeader> {
|
||||
let mut parts = jwt.split('.');
|
||||
let jwt_header_b64 = parts.next().ok_or(anyhow!("could not split jwt"))?;
|
||||
|
||||
let jwt_header: JwtHeader = serde_json::from_slice(
|
||||
&Base64UrlSafeNoPadding::decode_to_vec(jwt_header_b64, None)?,
|
||||
)?;
|
||||
|
||||
Ok(jwt_header)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod decode_jwt_header;
|
||||
@@ -0,0 +1,29 @@
|
||||
//! google_sign_in
|
||||
//!
|
||||
//! The purpose of this library is to make single sign on (SSO) for Google easy to use.
|
||||
//!
|
||||
|
||||
// Never allow these
|
||||
#![forbid(private_bounds)]
|
||||
#![forbid(private_interfaces)]
|
||||
#![forbid(unused_must_use)] // NB: It's unsafe to not close/check some things
|
||||
|
||||
// Okay to toggle
|
||||
#![forbid(unreachable_patterns)]
|
||||
//#![forbid(unused_imports)]
|
||||
#![forbid(unused_mut)]
|
||||
//#![forbid(unused_variables)]
|
||||
|
||||
// Always allow
|
||||
#![allow(dead_code)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
pub mod certs;
|
||||
pub mod claims;
|
||||
pub mod decode_and_verify_token_claims;
|
||||
pub mod errors;
|
||||
|
||||
pub(crate) mod jwt;
|
||||
|
||||
// Export for external users
|
||||
pub use jwt_simple::common::VerificationOptions;
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "buckets"
|
||||
name = "bucket_paths"
|
||||
edition = "2018"
|
||||
version = "0.0.1"
|
||||
authors = [
|
||||
@@ -9,24 +9,21 @@ authors = [
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "buckets"
|
||||
name = "bucket_paths"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
normal = ["my-workspace-hack"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
# Internal
|
||||
crockford = { path = "../../lib/data/crockford" }
|
||||
#errors = { path = "../../lib/errors" }
|
||||
|
||||
|
||||
# Workspace hack (faster compile times)
|
||||
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
|
||||
|
||||
# Internal
|
||||
crockford = { path = "../data/crockford" }
|
||||
|
||||
# External
|
||||
# (none yet)
|
||||
# (None yet)
|
||||
|
||||
[dev-dependencies]
|
||||
# (none yet)
|
||||
# None yet
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod old_bespoke_paths;
|
||||
pub mod remote_file_manager_paths;
|
||||
pub mod typified_paths;
|
||||
+2
-4
@@ -1,7 +1,6 @@
|
||||
use crate::legacy::old_bespoke_paths::hashed_directory_path_short_string::hashed_directory_path_short_string;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use buckets::util::hashed_directory_path_short_string::hashed_directory_path_short_string;
|
||||
|
||||
/// This is designed to make it centrally configurable where
|
||||
/// different types of objects are stored.
|
||||
#[deprecated(note = "see the 'buckets' crate for a better approach")]
|
||||
@@ -298,10 +297,9 @@ impl BucketPathUnifier {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::legacy::old_bespoke_paths::bucket_path_unifier::BucketPathUnifier;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::bucket_path_unifier::BucketPathUnifier;
|
||||
|
||||
fn get_instance() -> BucketPathUnifier {
|
||||
BucketPathUnifier {
|
||||
// TTS
|
||||
+1
-1
@@ -17,7 +17,7 @@ pub fn hashed_directory_path_short_string(file_hash: &str) -> String {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::util::hashed_directory_path_short_string::hashed_directory_path_short_string;
|
||||
use crate::legacy::old_bespoke_paths::hashed_directory_path_short_string::hashed_directory_path_short_string;
|
||||
|
||||
#[test]
|
||||
fn test_length_zero() {
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod bucket_path_unifier;
|
||||
pub (crate) mod hashed_directory_path_short_string;
|
||||
+7
-9
@@ -1,10 +1,8 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use buckets::util::hashed_directory_path_long_string;
|
||||
use crate::legacy::remote_file_manager_paths::file_descriptor::FileDescriptor;
|
||||
use crate::legacy::remote_file_manager_paths::remote_cloud_bucket_details::RemoteCloudBucketDetails;
|
||||
use crate::util::hashed_directory_path_long_string::hashed_directory_path_long_string;
|
||||
use crockford::crockford_entropy_lower;
|
||||
|
||||
use crate::remote_file_manager::file_descriptor::FileDescriptor;
|
||||
use crate::remote_file_manager::remote_cloud_bucket_details::RemoteCloudBucketDetails;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct FileBucketDirectory {
|
||||
file_object_hash: String,
|
||||
@@ -23,7 +21,7 @@ pub struct FileBucketDirectory {
|
||||
pub fn from_existing_bucket_details(bucket_details:RemoteCloudBucketDetails) -> Self {
|
||||
let file_descriptor = bucket_details.file_descriptor_from_bucket_details();
|
||||
let entropy = bucket_details.get_object_hash();
|
||||
let middle = hashed_directory_path_long_string::hashed_directory_path_long_string(entropy.as_ref());
|
||||
let middle = hashed_directory_path_long_string(entropy.as_ref());
|
||||
// gets you cloud bucket path e.g weights/a/b/c/d/clould_path_entropy
|
||||
let remote_cloud_base_directory = format!("{}/{}{}", file_descriptor.remote_directory_path(), middle, entropy);
|
||||
// gets you name of the file with suffix and prefix and entropy in the centre
|
||||
@@ -43,7 +41,7 @@ pub struct FileBucketDirectory {
|
||||
pub fn new_from_file_descriptor(file_descriptor: Box<dyn FileDescriptor>) -> Self {
|
||||
let entropy = crockford_entropy_lower(32);
|
||||
// gets you wiki /a/b/c/d folder structure
|
||||
let middle = hashed_directory_path_long_string::hashed_directory_path_long_string(entropy.as_ref());
|
||||
let middle = hashed_directory_path_long_string(entropy.as_ref());
|
||||
// gets you cloud bucket path e.g weights/a/b/c/d/clould_path_entropy
|
||||
let remote_cloud_base_directory = format!("{}/{}{}", file_descriptor.remote_directory_path(), middle, entropy);
|
||||
// gets you name of the file with suffix and prefix and entropy in the centre
|
||||
@@ -76,9 +74,9 @@ pub struct FileBucketDirectory {
|
||||
pub fn to_full_remote_cloud_file_path_pathbuf(&self) -> PathBuf {
|
||||
PathBuf::from(&self.full_remote_cloud_file_path)
|
||||
}
|
||||
|
||||
pub fn get_file_name(&self) -> &str {
|
||||
&self.file_name
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
use crate::remote_file_manager::file_descriptor::FileDescriptor;
|
||||
use crate::legacy::remote_file_manager_paths::file_descriptor::FileDescriptor;
|
||||
|
||||
// TODO ASK BRANDON WHAT path should this be?
|
||||
const MEDIA_FILE_DIRECTORY: &str = "/media";
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod file_descriptor;
|
||||
pub mod file_directory;
|
||||
pub mod media_descriptor;
|
||||
pub mod remote_cloud_bucket_details;
|
||||
pub mod weights_descriptor;
|
||||
+3
-14
@@ -1,7 +1,6 @@
|
||||
use crate::remote_file_manager::media_descriptor;
|
||||
use crate::remote_file_manager::weights_descriptor::{self};
|
||||
|
||||
use super::file_descriptor::FileDescriptor;
|
||||
use crate::legacy::remote_file_manager_paths::file_descriptor::FileDescriptor;
|
||||
use crate::legacy::remote_file_manager_paths::media_descriptor;
|
||||
use crate::legacy::remote_file_manager_paths::weights_descriptor;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RemoteCloudBucketDetails {
|
||||
@@ -73,13 +72,3 @@ impl RemoteCloudBucketDetails {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
pub fn test() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+4
-4
@@ -101,7 +101,7 @@ impl FileDescriptor for WeightsSDXLDescriptor {
|
||||
|
||||
pub struct WeightsSVCDescriptor;
|
||||
|
||||
impl FileDescriptor for crate::remote_file_manager::weights_descriptor::WeightsSVCDescriptor {
|
||||
impl FileDescriptor for WeightsSVCDescriptor {
|
||||
fn remote_directory_path(&self) -> &str {
|
||||
REMOTE_FILE_DIRECTORY
|
||||
}
|
||||
@@ -125,7 +125,7 @@ impl FileDescriptor for crate::remote_file_manager::weights_descriptor::WeightsS
|
||||
|
||||
pub struct WeightsRVCDescriptor;
|
||||
|
||||
impl FileDescriptor for crate::remote_file_manager::weights_descriptor::WeightsRVCDescriptor {
|
||||
impl FileDescriptor for WeightsRVCDescriptor {
|
||||
fn remote_directory_path(&self) -> &str {
|
||||
REMOTE_FILE_DIRECTORY
|
||||
}
|
||||
@@ -149,7 +149,7 @@ impl FileDescriptor for crate::remote_file_manager::weights_descriptor::WeightsR
|
||||
|
||||
pub struct WeightsRVCIndexDescriptor;
|
||||
|
||||
impl FileDescriptor for crate::remote_file_manager::weights_descriptor::WeightsRVCIndexDescriptor {
|
||||
impl FileDescriptor for WeightsRVCIndexDescriptor {
|
||||
fn remote_directory_path(&self) -> &str {
|
||||
REMOTE_FILE_DIRECTORY
|
||||
}
|
||||
@@ -173,7 +173,7 @@ impl FileDescriptor for crate::remote_file_manager::weights_descriptor::WeightsR
|
||||
|
||||
pub struct WeightsVallePromptDescriptor;
|
||||
|
||||
impl FileDescriptor for crate::remote_file_manager::weights_descriptor::WeightsVallePromptDescriptor {
|
||||
impl FileDescriptor for WeightsVallePromptDescriptor {
|
||||
fn remote_directory_path(&self) -> &str {
|
||||
REMOTE_FILE_DIRECTORY
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod private;
|
||||
pub mod public;
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
use crockford::crockford_entropy_lower;
|
||||
|
||||
use crate::private::private_path::PrivatePath;
|
||||
use crate::legacy::typified_paths::private::private_path::PrivatePath;
|
||||
use crate::util::hashed_directory_path_long_string::hashed_directory_path_long_string;
|
||||
|
||||
// TODO: Generate these from a macro.
|
||||
@@ -81,7 +81,7 @@ impl ZeroShotVoiceEmbeddingBucketDirectory {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::private::zs_voices::bucket_directory::{ModelCategory, ModelType, ZeroShotVoiceEmbeddingBucketDirectory};
|
||||
use crate::legacy::typified_paths::private::zs_voices::bucket_directory::{ModelCategory, ModelType, ZeroShotVoiceEmbeddingBucketDirectory};
|
||||
|
||||
#[test]
|
||||
pub fn generate_new_entropy() {
|
||||
+6
-6
@@ -2,8 +2,8 @@ use std::path::PathBuf;
|
||||
|
||||
use crockford::crockford_entropy_lower;
|
||||
|
||||
use crate::private::private_path::PrivatePath;
|
||||
use crate::private::zs_voices::bucket_directory::{ModelCategory, ModelType, ZeroShotVoiceEmbeddingBucketDirectory};
|
||||
use crate::legacy::typified_paths::private::private_path::PrivatePath;
|
||||
use crate::legacy::typified_paths::private::zs_voices::bucket_directory::{ModelCategory, ModelType, ZeroShotVoiceEmbeddingBucketDirectory};
|
||||
|
||||
// TODO: Generate these from a macro.
|
||||
|
||||
@@ -74,8 +74,8 @@ mod tests {
|
||||
use std::path::PathBuf;
|
||||
|
||||
mod with_prefix_and_extension {
|
||||
use crate::private::zs_voices::bucket_directory::ModelCategory;
|
||||
use crate::private::zs_voices::bucket_file_path::{ModelType, ZeroShotVoiceEmbeddingBucketPath};
|
||||
use crate::legacy::typified_paths::private::zs_voices::bucket_directory::ModelCategory;
|
||||
use crate::legacy::typified_paths::private::zs_voices::bucket_file_path::{ModelType, ZeroShotVoiceEmbeddingBucketPath};
|
||||
|
||||
#[test]
|
||||
pub fn generate_new_entropy() {
|
||||
@@ -94,8 +94,8 @@ mod tests {
|
||||
}
|
||||
|
||||
mod without_prefix_and_extension {
|
||||
use crate::private::zs_voices::bucket_directory::ModelCategory;
|
||||
use crate::private::zs_voices::bucket_file_path::{ModelType, ZeroShotVoiceEmbeddingBucketPath};
|
||||
use crate::legacy::typified_paths::private::zs_voices::bucket_directory::ModelCategory;
|
||||
use crate::legacy::typified_paths::private::zs_voices::bucket_file_path::{ModelType, ZeroShotVoiceEmbeddingBucketPath};
|
||||
|
||||
use super::*;
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
use crockford::crockford_entropy_lower;
|
||||
|
||||
use crate::public::public_path::PublicPath;
|
||||
use crate::legacy::typified_paths::public::public_path::PublicPath;
|
||||
use crate::util::hashed_directory_path_long_string::hashed_directory_path_long_string;
|
||||
|
||||
// TODO: Generate these from a macro.
|
||||
@@ -46,7 +46,7 @@ impl MediaFileBucketDirectory {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::public::media_files::bucket_directory::MediaFileBucketDirectory;
|
||||
use crate::legacy::typified_paths::public::media_files::bucket_directory::MediaFileBucketDirectory;
|
||||
|
||||
#[test]
|
||||
pub fn generate_new_entropy() {
|
||||
+3
-3
@@ -2,8 +2,8 @@ use std::path::PathBuf;
|
||||
|
||||
use crockford::crockford_entropy_lower;
|
||||
|
||||
use crate::public::media_files::bucket_directory::MediaFileBucketDirectory;
|
||||
use crate::public::public_path::PublicPath;
|
||||
use crate::legacy::typified_paths::public::media_files::bucket_directory::MediaFileBucketDirectory;
|
||||
use crate::legacy::typified_paths::public::public_path::PublicPath;
|
||||
|
||||
const ORIGINAL_FILE_BASENAME : &str = "file.bin";
|
||||
|
||||
@@ -87,7 +87,7 @@ impl MediaFileBucketPath {
|
||||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use crate::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
|
||||
mod with_prefix_and_extension {
|
||||
use super::*;
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
use crockford::crockford_entropy_lower;
|
||||
|
||||
use crate::public::public_path::PublicPath;
|
||||
use crate::legacy::typified_paths::public::public_path::PublicPath;
|
||||
use crate::util::hashed_directory_path_long_string::hashed_directory_path_long_string;
|
||||
|
||||
// TODO: Generate these from a macro.
|
||||
@@ -47,7 +47,7 @@ impl MediaUploadDirectory {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::public::media_uploads::bucket_directory::MediaUploadDirectory;
|
||||
use crate::legacy::typified_paths::public::media_uploads::bucket_directory::MediaUploadDirectory;
|
||||
|
||||
#[test]
|
||||
pub fn generate_new_entropy() {
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user