zrb 0.0.51__py3-none-any.whl → 0.0.53__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. zrb/builtin/generator/fastapp/add.py +3 -2
  2. zrb/builtin/generator/fastapp/template/_automate/snake_app_name/cmd/start.sh +1 -1
  3. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/.gitignore +1 -0
  4. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/config.py +14 -5
  5. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/.env.local +4 -0
  6. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/.gitignore +1 -1
  7. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/README.md +0 -0
  8. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/package-lock.json +22 -0
  9. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/package.json +3 -0
  10. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/playwright.config.ts +0 -0
  11. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/postcss.config.js +0 -0
  12. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/app.css +0 -0
  13. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/app.d.ts +0 -0
  14. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/app.html +0 -0
  15. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/index.test.ts +0 -0
  16. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/lib/auth/helper.ts +146 -0
  17. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/lib/auth/store.ts +4 -0
  18. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/lib/auth/type.ts +10 -0
  19. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/lib/components/navigation/Menu.svelte +19 -17
  20. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/lib/components/navigation/Navigation.svelte +64 -7
  21. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/lib/components/navigation/helper.ts +18 -0
  22. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/lib/components/navigation/type.ts +0 -0
  23. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/lib/config/app.ts +9 -0
  24. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/lib/config/navData.ts +6 -17
  25. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/lib/error/helper.ts +12 -0
  26. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/routes/+error.svelte +8 -0
  27. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/routes/+layout.js +0 -0
  28. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/routes/+layout.svelte +6 -5
  29. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/routes/+page.svelte +2 -8
  30. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/static/favicon.png +0 -0
  31. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/static/logo.png +0 -0
  32. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/svelte.config.js +0 -0
  33. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/tailwind.config.js +0 -0
  34. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/tests/test.ts +0 -0
  35. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/tsconfig.json +0 -0
  36. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/vite.config.ts +0 -0
  37. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/component/__init__.py +8 -4
  38. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/component/access_token_scheme.py +14 -0
  39. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/component/access_token_util.py +17 -0
  40. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/component/bearer_token_scheme.py +5 -0
  41. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/component/model/user_model.py +9 -4
  42. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/component/refresh_token_util.py +17 -0
  43. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/core/__init__.py +14 -9
  44. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/core/{token_scheme/oauth2_bearer_token_scheme.py → access_token/scheme.py} +11 -11
  45. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/core/access_token/util.py +69 -0
  46. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/core/authorizer/rpc_authorizer.py +4 -2
  47. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/core/{token_util/jwt_token_util.py → refresh_token/util.py} +18 -6
  48. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/entity/group/api.py +9 -7
  49. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/entity/group/rpc.py +4 -4
  50. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/entity/permission/api.py +9 -7
  51. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/entity/permission/rpc.py +4 -4
  52. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/entity/user/api.py +27 -16
  53. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/entity/user/model.py +44 -19
  54. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/entity/user/rpc.py +19 -11
  55. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/schema/request.py +10 -0
  56. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/schema/token.py +7 -1
  57. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/requirements.txt +1 -0
  58. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/template.env +7 -3
  59. zrb/builtin/generator/fastapp_crud/add.py +7 -21
  60. zrb/builtin/generator/fastapp_crud/add_navigation.py +32 -0
  61. zrb/builtin/generator/fastapp_crud/template/src/kebab-app-name/src/frontend/src/routes/kebab-module-name/kebab-entity-name/+page.svelte +121 -0
  62. zrb/builtin/generator/fastapp_crud/template/src/kebab-app-name/src/frontend/src/routes/kebab-module-name/kebab-entity-name/delete/[id]/+page.svelte +75 -0
  63. zrb/builtin/generator/fastapp_crud/template/src/kebab-app-name/src/frontend/src/routes/kebab-module-name/kebab-entity-name/delete/[id]/+page.ts +5 -0
  64. zrb/builtin/generator/fastapp_crud/template/src/kebab-app-name/src/frontend/src/routes/kebab-module-name/kebab-entity-name/detail/[id]/+page.svelte +54 -0
  65. zrb/builtin/generator/fastapp_crud/template/src/kebab-app-name/src/frontend/src/routes/kebab-module-name/kebab-entity-name/detail/[id]/+page.ts +5 -0
  66. zrb/builtin/generator/fastapp_crud/template/src/kebab-app-name/src/frontend/src/routes/kebab-module-name/kebab-entity-name/new/+page.svelte +52 -0
  67. zrb/builtin/generator/fastapp_crud/template/src/kebab-app-name/src/frontend/src/routes/kebab-module-name/kebab-entity-name/update/[id]/+page.svelte +78 -0
  68. zrb/builtin/generator/fastapp_crud/template/src/kebab-app-name/src/frontend/src/routes/kebab-module-name/kebab-entity-name/update/[id]/+page.ts +5 -0
  69. zrb/builtin/generator/fastapp_crud/template/src/kebab-app-name/src/module/snake_module_name/entity/snake_entity_name/api.py +9 -7
  70. zrb/builtin/generator/fastapp_crud/template/src/kebab-app-name/src/module/snake_module_name/entity/snake_entity_name/rpc.py +4 -4
  71. zrb/builtin/generator/fastapp_field/add.py +203 -4
  72. zrb/helper/util.py +4 -0
  73. zrb/task/resource_maker.py +2 -1
  74. {zrb-0.0.51.dist-info → zrb-0.0.53.dist-info}/METADATA +6 -5
  75. {zrb-0.0.51.dist-info → zrb-0.0.53.dist-info}/RECORD +66 -52
  76. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/lib/config/config.ts +0 -4
  77. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/routes/about/+page.svelte +0 -2
  78. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/routes/greetings/[slug]/+page.js +0 -6
  79. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/routes/greetings/[slug]/+page.svelte +0 -5
  80. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/routes/sample/+page.svelte +0 -37
  81. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/frontend/src/routes/sample/delete/[id]/+page.svelte +0 -1
  82. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/component/token_scheme.py +0 -11
  83. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/component/token_util.py +0 -17
  84. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/core/token_scheme/token_sheme.py +0 -5
  85. zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/core/token_util/token_util.py +0 -13
  86. /zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/core/{token_scheme/__init__.py → access_token/_init_.py} +0 -0
  87. /zrb/builtin/generator/fastapp/template/src/kebab-app-name/src/module/auth/core/{token_util/__init__.py → refresh_token/_init_.py} +0 -0
  88. {zrb-0.0.51.dist-info → zrb-0.0.53.dist-info}/LICENSE +0 -0
  89. {zrb-0.0.51.dist-info → zrb-0.0.53.dist-info}/WHEEL +0 -0
  90. {zrb-0.0.51.dist-info → zrb-0.0.53.dist-info}/entry_points.txt +0 -0
@@ -67,8 +67,9 @@ copy_resource = ResourceMaker(
67
67
  excludes=[
68
68
  '*/deployment/venv',
69
69
  '*/src/kebab-app-name/venv',
70
- '*/src/kebab-app-name/frontend/node_modules',
71
- '*/src/kebab-app-name/frontend/build',
70
+ '*/src/kebab-app-name/src/frontend/node_modules',
71
+ '*/src/kebab-app-name/src/frontend/build',
72
+ '*/src/kebab-app-name/src/frontend/.svelte-kit',
72
73
  ]
73
74
  )
74
75
 
@@ -3,4 +3,4 @@ echo "Activate venv"
3
3
  source venv/bin/activate
4
4
 
5
5
  echo "Start app"
6
- uvicorn main:app --host {{env.get("APP_HOST", "0.0.0.0")}} --port {{env.get("APP_PORT", "8080")}} {{ "--reload" if util.to_boolean(env.get("APP_RELOAD", "true")) else "" }}
6
+ uvicorn main:app --host {{env.get("APP_HOST", "0.0.0.0")}} --port {{env.get("APP_PORT", "8080")}} {{ "--reload --reload-include index.html" if util.to_boolean(env.get("APP_RELOAD", "true")) else "" }}
@@ -1,4 +1,5 @@
1
1
  .env
2
2
  frontend/build
3
+ frontend/build
3
4
  __pycache__
4
5
  venv
@@ -13,13 +13,22 @@ app_host = os.getenv('APP_HOST', '0.0.0.0')
13
13
  app_port = int(os.getenv('APP_PORT', '8080'))
14
14
  app_reload = str_to_boolean(os.getenv('APP_RELOAD', 'true'))
15
15
  app_max_not_ready = int(os.getenv('APP_MAX_NOT_READY', '10'))
16
- app_auth_token_cookie_key = os.getenv(
17
- 'PUBLIC_AUTH_TOKEN_COOKIE_KEY', 'auth_token'
16
+
17
+ app_auth_access_token_cookie_key = os.getenv(
18
+ 'PUBLIC_AUTH_ACCESS_TOKEN_COOKIE_KEY', 'access_token'
19
+ )
20
+ app_auth_refresh_token_cookie_key = os.getenv(
21
+ 'PUBLIC_AUTH_REFRESH_TOKEN_COOKIE_KEY', 'refresh_token'
18
22
  )
19
- app_auth_token_expire_seconds = int(os.getenv(
20
- 'APP_AUTH_TOKEN_EXPIRE_SECONDS', '300'
23
+
24
+ app_auth_access_token_type = os.getenv('APP_AUTH_ACCESS_TOKEN_TYPE', 'jwt')
25
+ app_auth_access_token_expire_seconds = int(os.getenv(
26
+ 'APP_AUTH_ACCESS_TOKEN_EXPIRE_SECONDS', '300'
27
+ ))
28
+ app_auth_refresh_token_type = os.getenv('APP_AUTH_REFRESH_TOKEN_TYPE', 'jwt')
29
+ app_auth_refresh_token_expire_seconds = int(os.getenv(
30
+ 'APP_AUTH_REFRESH_TOKEN_EXPIRE_SECONDS', '86400'
21
31
  ))
22
- app_auth_token_type = os.getenv('APP_AUTH_TOKEN_TYPE', 'jwt')
23
32
  app_auth_jwt_token_secret_key = os.getenv(
24
33
  'APP_AUTH_JWT_TOKEN_SECRET_KEY', 'secret'
25
34
  )
@@ -0,0 +1,4 @@
1
+ PUBLIC_BRAND=
2
+ PUBLIC_TITLE=
3
+ PUBLIC_AUTH_ACCESS_TOKEN_COOKIE_KEY=
4
+ PUBLIC_AUTH_REFRESH_TOKEN_COOKIE_KEY=
@@ -4,7 +4,7 @@ node_modules
4
4
  /.svelte-kit
5
5
  /package
6
6
  .env
7
- .env.*
7
+ # .env.*
8
8
  !.env.example
9
9
  vite.config.js.timestamp-*
10
10
  vite.config.ts.timestamp-*
@@ -10,6 +10,8 @@
10
10
  "dependencies": {
11
11
  "axios": "^1.4.0",
12
12
  "daisyui": "^2.51.6",
13
+ "js-cookie": "^3.0.5",
14
+ "jwt-decode": "^3.1.2",
13
15
  "process": "^0.11.10"
14
16
  },
15
17
  "devDependencies": {
@@ -17,6 +19,7 @@
17
19
  "@sveltejs/adapter-auto": "^2.0.0",
18
20
  "@sveltejs/adapter-static": "^2.0.1",
19
21
  "@sveltejs/kit": "^1.5.0",
22
+ "@types/js-cookie": "^3.0.3",
20
23
  "@typescript-eslint/eslint-plugin": "^5.45.0",
21
24
  "@typescript-eslint/parser": "^5.45.0",
22
25
  "autoprefixer": "^10.4.14",
@@ -351,6 +354,12 @@
351
354
  "integrity": "sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==",
352
355
  "dev": true
353
356
  },
357
+ "node_modules/@types/js-cookie": {
358
+ "version": "3.0.3",
359
+ "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.3.tgz",
360
+ "integrity": "sha512-Xe7IImK09HP1sv2M/aI+48a20VX+TdRJucfq4vfRVy6nWN8PYPOEnlMRSgxJAgYQIXJVL8dZ4/ilAM7dWNaOww==",
361
+ "dev": true
362
+ },
354
363
  "node_modules/@types/json-schema": {
355
364
  "version": "7.0.11",
356
365
  "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@@ -1850,6 +1859,14 @@
1850
1859
  "jiti": "bin/jiti.js"
1851
1860
  }
1852
1861
  },
1862
+ "node_modules/js-cookie": {
1863
+ "version": "3.0.5",
1864
+ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
1865
+ "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
1866
+ "engines": {
1867
+ "node": ">=14"
1868
+ }
1869
+ },
1853
1870
  "node_modules/js-sdsl": {
1854
1871
  "version": "4.4.0",
1855
1872
  "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz",
@@ -1884,6 +1901,11 @@
1884
1901
  "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
1885
1902
  "dev": true
1886
1903
  },
1904
+ "node_modules/jwt-decode": {
1905
+ "version": "3.1.2",
1906
+ "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
1907
+ "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
1908
+ },
1887
1909
  "node_modules/kleur": {
1888
1910
  "version": "4.1.5",
1889
1911
  "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
@@ -19,6 +19,7 @@
19
19
  "@sveltejs/adapter-auto": "^2.0.0",
20
20
  "@sveltejs/adapter-static": "^2.0.1",
21
21
  "@sveltejs/kit": "^1.5.0",
22
+ "@types/js-cookie": "^3.0.3",
22
23
  "@typescript-eslint/eslint-plugin": "^5.45.0",
23
24
  "@typescript-eslint/parser": "^5.45.0",
24
25
  "autoprefixer": "^10.4.14",
@@ -40,6 +41,8 @@
40
41
  "dependencies": {
41
42
  "axios": "^1.4.0",
42
43
  "daisyui": "^2.51.6",
44
+ "js-cookie": "^3.0.5",
45
+ "jwt-decode": "^3.1.2",
43
46
  "process": "^0.11.10"
44
47
  }
45
48
  }
@@ -0,0 +1,146 @@
1
+ import axios from 'axios';
2
+ import jwt_decode from 'jwt-decode';
3
+ import { authAccessTokenCookieKey, authRefreshTokenCookieKey, isAuthorizedApiUrl, loginApiUrl, refreshTokenApiUrl } from '../config/app';
4
+ import { userIdStore, userNameStore } from './store';
5
+ import type { AccessTokenData } from './type';
6
+ import Cookies from 'js-cookie';
7
+
8
+
9
+ export async function getAuthorization(permissions: string[]): Promise<{[key: string]: boolean}> {
10
+ try {
11
+ const accessToken = await ensureAccessToken();
12
+ if (accessToken == '') {
13
+ return {};
14
+ }
15
+ const response = await axios.post(
16
+ isAuthorizedApiUrl,
17
+ {permission_names: permissions},
18
+ {
19
+ headers: { Authorization: `Bearer ${accessToken}` }
20
+ }
21
+ );
22
+ if (response && response.status == 200 && response.data) {
23
+ return response.data;
24
+ }
25
+ throw new Error('Invalid response');
26
+ } catch(error) {
27
+ console.error(error);
28
+ }
29
+ return {};
30
+ }
31
+
32
+ export async function initAuthStore() {
33
+ try {
34
+ const accessToken = await ensureAccessToken();
35
+ if (accessToken) {
36
+ setAuthStoreByAccessToken(accessToken);
37
+ }
38
+ } catch(error) {
39
+ console.error(error);
40
+ }
41
+ }
42
+
43
+ export async function ensureAccessToken(): Promise<string> {
44
+ try {
45
+ const oldAccessToken = getOldAccessToken();
46
+ if (oldAccessToken) {
47
+ const oldAccessTokenData: AccessTokenData = decodeAccessToken(oldAccessToken);
48
+ const { expireAt } = oldAccessTokenData;
49
+ const now = new Date();
50
+ if (now.getTime()/1000 < expireAt) {
51
+ return oldAccessToken;
52
+ }
53
+ }
54
+ const oldRefreshToken = getOldRefreshToken();
55
+ if (oldRefreshToken) {
56
+ const response = await axios.post(
57
+ refreshTokenApiUrl,
58
+ {access_token: oldAccessToken},
59
+ {
60
+ headers: { Authorization: `Bearer ${oldRefreshToken}` }
61
+ }
62
+ );
63
+ if (response && response.status == 200 && response.data && response.data.access_token && response.data.refresh_token) {
64
+ const newAccessToken: string = response.data.access_token;
65
+ const newRefreshToken: string = response.data.refresh_token;
66
+ saveToken(newAccessToken, newRefreshToken);
67
+ setAuthStoreByAccessToken(newAccessToken);
68
+ return newAccessToken;
69
+ }
70
+ throw new Error('Invalid refresh-token response');
71
+ }
72
+ throw new Error('Cannot refresh token');
73
+ } catch(error) {
74
+ logout();
75
+ throw(error);
76
+ }
77
+ }
78
+
79
+ function getOldRefreshToken(): string | null {
80
+ return localStorage.getItem(authRefreshTokenCookieKey);
81
+ }
82
+
83
+ function getOldAccessToken(): string | undefined {
84
+ return Cookies.get(authAccessTokenCookieKey);
85
+ }
86
+
87
+ export async function login(identity: string, password: string): Promise<boolean> {
88
+ try {
89
+ const response = await axios.post(loginApiUrl, {identity, password});
90
+ if (response && response.status == 200 && response.data && response.data.access_token && response.data.refresh_token) {
91
+ const accessToken: string = response.data.access_token;
92
+ const refreshToken: string = response.data.refresh_token;
93
+ saveToken(accessToken, refreshToken);
94
+ setAuthStoreByAccessToken(accessToken);
95
+ return true;
96
+ }
97
+ } catch(error) {
98
+ console.error(error);
99
+ }
100
+ logout();
101
+ return false;
102
+ }
103
+
104
+ function saveToken(accessToken: string, refreshToken: string) {
105
+ Cookies.set(authAccessTokenCookieKey, accessToken);
106
+ localStorage.setItem(authRefreshTokenCookieKey, refreshToken);
107
+ }
108
+
109
+ export function logout() {
110
+ Cookies.remove(authAccessTokenCookieKey);
111
+ localStorage.removeItem(authRefreshTokenCookieKey);
112
+ unsetAuthStore();
113
+ }
114
+
115
+ function unsetAuthStore() {
116
+ setAuthStore('', '');
117
+ }
118
+
119
+ function setAuthStoreByAccessToken(accessToken: string) {
120
+ const tokenData = decodeAccessToken(accessToken);
121
+ setAuthStore(tokenData.sub.userId, tokenData.sub.userName);
122
+ }
123
+
124
+ function setAuthStore(newUserId: string, newUserName: string) {
125
+ userIdStore.set(newUserId);
126
+ userNameStore.set(newUserName);
127
+ }
128
+
129
+ function decodeAccessToken(accessToken: string): AccessTokenData {
130
+ const jwtTokenData: {
131
+ exp: number,
132
+ sub: {
133
+ user_id: string,
134
+ username: string,
135
+ expire_seconds: number
136
+ }
137
+ } = jwt_decode(accessToken);
138
+ return {
139
+ sub: {
140
+ userId: jwtTokenData.sub.user_id,
141
+ userName: jwtTokenData.sub.username,
142
+ expireSeconds: jwtTokenData.sub.expire_seconds,
143
+ },
144
+ expireAt: jwtTokenData.exp
145
+ }
146
+ }
@@ -0,0 +1,4 @@
1
+ import { writable } from 'svelte/store';
2
+
3
+ export const userIdStore = writable('');
4
+ export const userNameStore = writable('');
@@ -0,0 +1,10 @@
1
+ export interface AccessTokenData {
2
+ sub: AccessTokenDataSub;
3
+ expireAt: number;
4
+ }
5
+
6
+ export interface AccessTokenDataSub {
7
+ userId: string;
8
+ userName: string;
9
+ expireSeconds: number;
10
+ }
@@ -1,21 +1,23 @@
1
1
  <script lang="ts">
2
2
  import type {SingleNavData} from './type';
3
- export let data: SingleNavData;
3
+ export let singleNavData: SingleNavData;
4
+ export let authorization: {[permission: string]: boolean} = {};
4
5
  </script>
5
- {#if 'submenus' in data && data.submenus}
6
- <!-- svelte-ignore a11y-no-noninteractive-tabindex -->
7
- <li tabindex="0">
8
- <!-- svelte-ignore a11y-invalid-attribute -->
9
- <a href="#">
10
- Parent
11
- <svg class="fill-current" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><path d="M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z"/></svg>
12
- </a>
13
- <ul class="p-2 bg-base-100 z-10">
14
- {#each data.submenus as submenu}
15
- <svelte:self data={submenu} />
16
- {/each}
17
- </ul>
18
- </li>
19
- {:else}
20
- <li><a href={data.url} class="px-4">{data.title}</a></li>
6
+ {#if (!singleNavData.permission || singleNavData.permission == '' || (authorization[singleNavData.permission]))}
7
+ {#if 'submenus' in singleNavData && singleNavData.submenus}
8
+ <!-- svelte-ignore a11y-no-noninteractive-tabindex -->
9
+ <li tabindex="0">
10
+ <a href="#top">
11
+ {singleNavData.title}
12
+ <svg class="fill-current" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><path d="M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z"/></svg>
13
+ </a>
14
+ <ul class="p-2 bg-base-100">
15
+ {#each singleNavData.submenus as submenu}
16
+ <svelte:self singleNavData={submenu} authorization={authorization} />
17
+ {/each}
18
+ </ul>
19
+ </li>
20
+ {:else}
21
+ <li><a href={singleNavData.url} class="px-4">{singleNavData.title}</a></li>
22
+ {/if}
21
23
  {/if}
@@ -1,23 +1,80 @@
1
1
  <script lang="ts">
2
- import type {SingleNavData} from './type';
2
+ import { goto } from '$app/navigation';
3
+ import { onMount } from 'svelte';
3
4
  import Menu from './Menu.svelte';
5
+ import type { SingleNavData } from './type';
6
+ import { getAuthorization, initAuthStore, login, logout } from '../../auth/helper';
7
+ import { userIdStore } from '../../auth/store';
8
+ import { getNavDataPermissions } from './helper';
9
+
4
10
  export let logo: string;
5
11
  export let brand: string;
6
- export let data: SingleNavData[];
7
- export let authTokenCookieKey: string;
12
+ export let navData: SingleNavData[];
13
+ export let loginTitle: string = 'Login';
14
+ export let logoutTitle: string = 'Logout';
15
+
16
+ const navDataPermissions = getNavDataPermissions(navData);
17
+
18
+ let identity: string;
19
+ let password: string;
20
+ let userId = '';
21
+ let authorization: {[key: string]: boolean} = {};
22
+
23
+ userIdStore.subscribe(async (value) => {
24
+ userId = value;
25
+ authorization = await getAuthorization(navDataPermissions);
26
+ });
27
+
28
+ onMount(async() => {
29
+ await initAuthStore();
30
+ });
31
+
32
+ async function onLoginClick() {
33
+ const loginSuccess = await login(identity, password);
34
+ if (loginSuccess) {
35
+ await goto('/');
36
+ return;
37
+ }
38
+ alert('salah');
39
+ }
40
+
41
+ async function onLogoutClick() {
42
+ logout();
43
+ await goto('/');
44
+ }
8
45
  </script>
9
46
 
10
- <div class="navbar bg-base-100">
47
+ <div class="navbar sticky top-0 bg-base-100 z-50">
11
48
  <div class="flex-1">
12
49
  <img class="h-8 mr-3" src={logo} alt="Logo">
13
50
  <a href="/" class="btn btn-ghost normal-case text-xl">{brand}</a>
14
51
  </div>
15
52
  <div class="flex-none">
16
53
  <ul class="menu menu-horizontal px-1">
17
- {#each data as menuData}
18
- <Menu data={menuData} />
54
+ {#each navData as singleNavData}
55
+ <Menu singleNavData={singleNavData} authorization={authorization} />
19
56
  {/each}
57
+ {#if userId == ''}
58
+ <li><a href="#login-modal" class="px-4">{loginTitle}</a></li>
59
+ {:else}
60
+ <li><a href="#top" class="px-4" on:click={onLogoutClick}>{logoutTitle}</a></li>
61
+ {/if}
20
62
  </ul>
21
63
  </div>
22
64
  </div>
23
- {authTokenCookieKey}
65
+
66
+ <div class="modal" id="login-modal">
67
+ <form class="modal-box">
68
+ <div class="mb-6">
69
+ <label class="block text-gray-700 font-bold mb-2" for="identity">Identity</label>
70
+ <input class="w-full px-3 py-2 border rounded-lg text-gray-700 focus:outline-none focus:shadow-outline" id="identity" type="text" placeholder="Enter your username/email/phone" bind:value={identity} />
71
+ </div>
72
+ <div class="mb-6">
73
+ <label class="block text-gray-700 font-bold mb-2" for="password">Password</label>
74
+ <input class="w-full px-3 py-2 border rounded-lg text-gray-700 focus:outline-none focus:shadow-outline" id="password" type="password" placeholder="Enter your password" bind:value={password} />
75
+ </div>
76
+ <div class="modal-action">
77
+ <a href="#top" class="btn btn-primary" on:click={onLoginClick}>Sign in</a>
78
+ </div>
79
+ </form>
80
+ </div>
@@ -0,0 +1,18 @@
1
+ import type { SingleNavData } from "./type";
2
+
3
+ export function getNavDataPermissions(navData: SingleNavData[]): string[] {
4
+ let permissions: string[] = [];
5
+ for (const singleNavData of navData) {
6
+ if (singleNavData.permission && singleNavData.permission != '') {
7
+ permissions.push(singleNavData.permission);
8
+ }
9
+ if (singleNavData.submenus) {
10
+ const subPermissions: string[] = getNavDataPermissions(singleNavData.submenus);
11
+ const uniqueSubPermissions = subPermissions.filter((value) => {
12
+ return permissions.indexOf(value) === -1
13
+ });
14
+ permissions = permissions.concat(uniqueSubPermissions)
15
+ }
16
+ }
17
+ return permissions
18
+ }
@@ -0,0 +1,9 @@
1
+ import {PUBLIC_BRAND, PUBLIC_TITLE, PUBLIC_AUTH_ACCESS_TOKEN_COOKIE_KEY, PUBLIC_AUTH_REFRESH_TOKEN_COOKIE_KEY} from '$env/static/public';
2
+ export const appBrand = PUBLIC_BRAND || 'PascalAppName';
3
+ export const appTitle = PUBLIC_TITLE || 'PascalAppName';
4
+ export const authAccessTokenCookieKey = PUBLIC_AUTH_ACCESS_TOKEN_COOKIE_KEY || 'access_token';
5
+ export const authRefreshTokenCookieKey = PUBLIC_AUTH_REFRESH_TOKEN_COOKIE_KEY || 'refresh_token';
6
+
7
+ export const loginApiUrl: string = '/api/v1/auth/login';
8
+ export const refreshTokenApiUrl: string = '/api/v1/auth/refresh-token';
9
+ export const isAuthorizedApiUrl: string = '/api/v1/auth/is-authorized';
@@ -1,25 +1,14 @@
1
1
  import type { SingleNavData } from '../components/navigation/type'
2
2
 
3
3
  export const navData: SingleNavData[] = [
4
- {title: "Home", url: "/"},
4
+ {title: 'Home', url: '/'},
5
5
  {
6
- title: "Auth",
7
- url: "#",
6
+ title: 'Auth',
7
+ url: '#',
8
8
  submenus: [
9
- {title: "Permission", url: "auth/permission"},
10
- {title: "Group", url: "auth/group"},
11
- {title: "User", url: "auth/user"},
9
+ {title: 'Permission', url: '/auth/permission', permission: 'auth:permission:get'},
10
+ {title: 'Group', url: '/auth/group', permission: 'auth:group:get'},
11
+ {title: 'User', url: '/auth/user', permission: 'auth:user:get'},
12
12
  ]
13
13
  },
14
- {title: "About", url: "/about"},
15
- {title: "Greetings, Lord", url: "/greetings/Lord"},
16
- {
17
- title: "Test",
18
- url: "#",
19
- submenus: [
20
- {title: "Sub 1", url: "/"},
21
- {title: "Sub 2 long long title", url: "/about"}
22
- ]
23
- },
24
- {title: "Sample url", url: "/sample"},
25
14
  ]
@@ -0,0 +1,12 @@
1
+ import axios from 'axios';
2
+
3
+ export function getErrorMessage(error: unknown): string {
4
+ if (axios.isAxiosError(error)) {
5
+ let errorMessage = error.message;
6
+ if (error.response?.data?.detail) {
7
+ errorMessage += JSON.stringify(error.response.data.detail);
8
+ }
9
+ return errorMessage;
10
+ }
11
+ return error + '';
12
+ }
@@ -0,0 +1,8 @@
1
+ <script lang="ts">
2
+ import { page } from '$app/stores';
3
+ </script>
4
+ <div class="flex flex-col justify-center items-center">
5
+ <h1 class="text-5xl">{$page.status}</h1>
6
+ <p class="text-3xl">{$page.error?.message}</p>
7
+ <a class="btn btn-primary mt-5" href="/">Let's go home</a>
8
+ </div>
@@ -1,13 +1,14 @@
1
1
  <script lang="ts">
2
- import "../app.css";
2
+ import { onMount } from 'svelte';
3
3
  import Navigation from '$lib/components/navigation/Navigation.svelte';
4
- import {navData} from '$lib/config/navData';
5
- import {appBrand, appTitle, appAuthTokenCookieKey} from '$lib/config/config';
6
- import logo from '$lib/assets/logo.png';
4
+ import { navData } from '$lib/config/navData';
5
+ import { appBrand, appTitle } from '$lib/config/app';
6
+ import logo from '/static/logo.png';
7
+ import "../app.css";
7
8
  </script>
8
9
 
9
10
  <title>{appTitle}</title>
10
- <Navigation data={navData} logo={logo} brand={appBrand} authTokenCookieKey={appAuthTokenCookieKey}></Navigation>
11
+ <Navigation navData={navData} logo={logo} brand={appBrand}></Navigation>
11
12
  <div class="pl-10 pr-10">
12
13
  <slot></slot>
13
14
  </div>
@@ -1,8 +1,2 @@
1
- <h1 class="text-3xl font-bold underline">Welcome to SvelteKit</h1>
2
- <p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
3
-
4
- <style lang="postcss">
5
- :global(html) {
6
- background-color: theme(colors.green.100);
7
- }
8
- </style>
1
+ <h1 class="text-3xl font-bold underline">Welcome to PascalAppName</h1>
2
+ <p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
@@ -1,8 +1,10 @@
1
1
  from module.auth.component.authorizer import authorizer
2
2
  from module.auth.component.base import Base
3
3
  from module.auth.component.password_hasher import password_hasher
4
- from module.auth.component.token_scheme import token_scheme
5
- from module.auth.component.token_util import token_util
4
+ from module.auth.component.access_token_scheme import access_token_scheme
5
+ from module.auth.component.access_token_util import access_token_util
6
+ from module.auth.component.bearer_token_scheme import bearer_token_scheme
7
+ from module.auth.component.refresh_token_util import refresh_token_util
6
8
  from module.auth.component.user import (
7
9
  admin_user, admin_user_password, guest_user
8
10
  )
@@ -10,8 +12,10 @@ from module.auth.component.user import (
10
12
  assert authorizer
11
13
  assert Base
12
14
  assert password_hasher
13
- assert token_scheme
14
- assert token_util
15
+ assert access_token_scheme
16
+ assert access_token_util
17
+ assert bearer_token_scheme
18
+ assert refresh_token_util
15
19
  assert admin_user
16
20
  assert admin_user_password
17
21
  assert guest_user
@@ -0,0 +1,14 @@
1
+ from module.auth.core import (
2
+ AccessTokenScheme, create_oauth2_bearer_access_token_scheme
3
+ )
4
+ from module.auth.component.access_token_util import access_token_util
5
+ from module.auth.component.user import guest_user
6
+ from config import app_auth_access_token_cookie_key
7
+
8
+
9
+ access_token_scheme: AccessTokenScheme = create_oauth2_bearer_access_token_scheme( # noqa
10
+ guest_user=guest_user,
11
+ access_token_util=access_token_util,
12
+ token_url='/api/v1/auth/login-oauth',
13
+ token_cookie_key=app_auth_access_token_cookie_key
14
+ )