calagoapi 1.0.dev1__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 (71) hide show
  1. calagoapi/__init__.py +190 -0
  2. calagoapi/account/__init__.py +14 -0
  3. calagoapi/account/account.py +292 -0
  4. calagoapi/account/auth.py +72 -0
  5. calagoapi/account/models.py +221 -0
  6. calagoapi/admin/__init__.py +48 -0
  7. calagoapi/admin/admin.py +341 -0
  8. calagoapi/admin/managers.py +490 -0
  9. calagoapi/admin/resources.py +970 -0
  10. calagoapi/aio/__init__.py +9 -0
  11. calagoapi/aio/console.py +289 -0
  12. calagoapi/aio/panel.py +176 -0
  13. calagoapi/api/__init__.py +63 -0
  14. calagoapi/api/account.py +195 -0
  15. calagoapi/api/admin.py +402 -0
  16. calagoapi/api/attachments.py +27 -0
  17. calagoapi/api/auth.py +52 -0
  18. calagoapi/api/backups.py +99 -0
  19. calagoapi/api/client.py +198 -0
  20. calagoapi/api/databases.py +217 -0
  21. calagoapi/api/files.py +210 -0
  22. calagoapi/api/network.py +84 -0
  23. calagoapi/api/public.py +13 -0
  24. calagoapi/api/schedules.py +79 -0
  25. calagoapi/api/servers.py +114 -0
  26. calagoapi/api/users.py +27 -0
  27. calagoapi/api/versions.py +29 -0
  28. calagoapi/backups/__init__.py +8 -0
  29. calagoapi/backups/backup.py +292 -0
  30. calagoapi/backups/manager.py +177 -0
  31. calagoapi/console/__init__.py +6 -0
  32. calagoapi/console/console.py +392 -0
  33. calagoapi/databases/__init__.py +12 -0
  34. calagoapi/databases/database.py +445 -0
  35. calagoapi/databases/explorer.py +159 -0
  36. calagoapi/databases/manager.py +122 -0
  37. calagoapi/files/__init__.py +9 -0
  38. calagoapi/files/file.py +196 -0
  39. calagoapi/files/manager.py +437 -0
  40. calagoapi/network/__init__.py +8 -0
  41. calagoapi/network/allocation.py +99 -0
  42. calagoapi/network/manager.py +132 -0
  43. calagoapi/nodes/__init__.py +6 -0
  44. calagoapi/nodes/node.py +477 -0
  45. calagoapi/panel.py +162 -0
  46. calagoapi/schedules/__init__.py +14 -0
  47. calagoapi/schedules/builders.py +461 -0
  48. calagoapi/schedules/manager.py +107 -0
  49. calagoapi/schedules/schedule.py +214 -0
  50. calagoapi/servers/__init__.py +13 -0
  51. calagoapi/servers/admin.py +430 -0
  52. calagoapi/servers/generic.py +785 -0
  53. calagoapi/servers/manager.py +167 -0
  54. calagoapi/servers/minecraft.py +207 -0
  55. calagoapi/tunnels/__init__.py +5 -0
  56. calagoapi/tunnels/tunnel.py +112 -0
  57. calagoapi/users/__init__.py +11 -0
  58. calagoapi/users/activity.py +45 -0
  59. calagoapi/users/subuser.py +127 -0
  60. calagoapi/users/user.py +267 -0
  61. calagoapi/utils/__init__.py +43 -0
  62. calagoapi/utils/convert.py +113 -0
  63. calagoapi/utils/exceptions.py +72 -0
  64. calagoapi/utils/model.py +142 -0
  65. calagoapi/utils/resource.py +100 -0
  66. calagoapi/utils/time.py +68 -0
  67. calagoapi-1.0.dev1.dist-info/METADATA +266 -0
  68. calagoapi-1.0.dev1.dist-info/RECORD +71 -0
  69. calagoapi-1.0.dev1.dist-info/WHEEL +5 -0
  70. calagoapi-1.0.dev1.dist-info/licenses/LICENSE +674 -0
  71. calagoapi-1.0.dev1.dist-info/top_level.txt +1 -0
calagoapi/__init__.py ADDED
@@ -0,0 +1,190 @@
1
+ from importlib.metadata import version as _pkg_version, PackageNotFoundError
2
+ from types import SimpleNamespace
3
+
4
+ # package version
5
+ try:
6
+ __version__ = _pkg_version('calagoapi')
7
+ except PackageNotFoundError:
8
+ __version__ = '0.0.0'
9
+
10
+ # modules
11
+ from .panel import Panel
12
+ from .aio import AsyncPanel, AsyncConsole
13
+ from .console import Console, ConsoleEvent
14
+ from .servers import GenericServer, MinecraftServer, AdminServer, ServerGroup
15
+ from .files import File
16
+ from .backups import Backup, BackupGroup
17
+ from .databases import Database, DatabaseInstance, InstanceDatabase, InstanceUser, DatabaseExplorer
18
+ from .schedules import Schedule, ScheduleStep, triggers, conditions, actions, variable
19
+ from .schedules import backups as backup_selectors
20
+ from .users import Subuser, User, Activity
21
+ from .network import Allocation, Subdomain
22
+ from .nodes import Node
23
+ from .account import ApiKey, SshKey, SecurityKey, Session, OAuthLink, CommandSnippet
24
+ from .admin import (
25
+ Location,
26
+ Nest,
27
+ Egg,
28
+ EggVariable,
29
+ Mount,
30
+ Device,
31
+ Role,
32
+ Announcement,
33
+ OAuthProvider,
34
+ DatabaseHost,
35
+ DatabaseAgentHost,
36
+ DatabaseAgentTemplate,
37
+ BackupConfiguration,
38
+ BackupPolicy,
39
+ EggRepository,
40
+ EggConfiguration,
41
+ SubdomainDomain,
42
+ )
43
+
44
+ from .utils import (
45
+ Page,
46
+ convert,
47
+ formatBytes,
48
+ formatDuration,
49
+ waitForState,
50
+ waitForOnline,
51
+ waitForOffline,
52
+ secsUntil,
53
+ minsUntil,
54
+ CalagoAPIError,
55
+ BadRequestError,
56
+ AuthenticationError,
57
+ PermissionDeniedError,
58
+ NotFoundError,
59
+ ConflictError,
60
+ RateLimitError,
61
+ ServerError,
62
+ PanelConnectionError,
63
+ ConsoleError,
64
+ InvalidActionError,
65
+ WaitTimeoutError,
66
+ )
67
+
68
+ # namespaces
69
+ utils = SimpleNamespace(
70
+ convert = convert,
71
+ formatBytes = formatBytes,
72
+ formatDuration = formatDuration,
73
+ waitForState = waitForState,
74
+ waitForOnline = waitForOnline,
75
+ waitForOffline = waitForOffline,
76
+ secsUntil = secsUntil,
77
+ minsUntil = minsUntil,
78
+ )
79
+
80
+ servers = SimpleNamespace(
81
+ Panel = Panel,
82
+ Generic = GenericServer,
83
+ Minecraft = MinecraftServer,
84
+ )
85
+
86
+ schedules = SimpleNamespace(
87
+ triggers = triggers,
88
+ conditions = conditions,
89
+ actions = actions,
90
+ backups = backup_selectors,
91
+ variable = variable,
92
+ )
93
+
94
+
95
+ __all__ = [
96
+ '__version__',
97
+
98
+ # core
99
+ 'Panel',
100
+ 'AsyncPanel',
101
+ 'Console',
102
+ 'AsyncConsole',
103
+ 'ConsoleEvent',
104
+ 'GenericServer',
105
+ 'MinecraftServer',
106
+ 'AdminServer',
107
+ 'ServerGroup',
108
+ 'Page',
109
+
110
+ # server features
111
+ 'File',
112
+ 'Backup',
113
+ 'BackupGroup',
114
+ 'Database',
115
+ 'DatabaseInstance',
116
+ 'InstanceDatabase',
117
+ 'InstanceUser',
118
+ 'DatabaseExplorer',
119
+ 'Schedule',
120
+ 'ScheduleStep',
121
+ 'Subuser',
122
+ 'Allocation',
123
+ 'Subdomain',
124
+ 'Activity',
125
+
126
+ # schedule builders
127
+ 'triggers',
128
+ 'conditions',
129
+ 'actions',
130
+ 'backup_selectors',
131
+ 'variable',
132
+
133
+ # account
134
+ 'ApiKey',
135
+ 'SshKey',
136
+ 'SecurityKey',
137
+ 'Session',
138
+ 'OAuthLink',
139
+ 'CommandSnippet',
140
+
141
+ # admin
142
+ 'User',
143
+ 'Node',
144
+ 'Location',
145
+ 'Nest',
146
+ 'Egg',
147
+ 'EggVariable',
148
+ 'Mount',
149
+ 'Device',
150
+ 'Role',
151
+ 'Announcement',
152
+ 'OAuthProvider',
153
+ 'DatabaseHost',
154
+ 'DatabaseAgentHost',
155
+ 'DatabaseAgentTemplate',
156
+ 'BackupConfiguration',
157
+ 'BackupPolicy',
158
+ 'EggRepository',
159
+ 'EggConfiguration',
160
+ 'SubdomainDomain',
161
+
162
+ # utilities
163
+ 'convert',
164
+ 'formatBytes',
165
+ 'formatDuration',
166
+ 'waitForState',
167
+ 'waitForOnline',
168
+ 'waitForOffline',
169
+ 'secsUntil',
170
+ 'minsUntil',
171
+
172
+ # exceptions
173
+ 'CalagoAPIError',
174
+ 'BadRequestError',
175
+ 'AuthenticationError',
176
+ 'PermissionDeniedError',
177
+ 'NotFoundError',
178
+ 'ConflictError',
179
+ 'RateLimitError',
180
+ 'ServerError',
181
+ 'PanelConnectionError',
182
+ 'ConsoleError',
183
+ 'InvalidActionError',
184
+ 'WaitTimeoutError',
185
+
186
+ # namespaces
187
+ 'utils',
188
+ 'servers',
189
+ 'schedules',
190
+ ]
@@ -0,0 +1,14 @@
1
+ from .account import Account
2
+ from .auth import Auth
3
+ from .models import ApiKey, SshKey, SecurityKey, Session, OAuthLink, CommandSnippet
4
+
5
+ __all__ = [
6
+ 'Account',
7
+ 'Auth',
8
+ 'ApiKey',
9
+ 'SshKey',
10
+ 'SecurityKey',
11
+ 'Session',
12
+ 'OAuthLink',
13
+ 'CommandSnippet',
14
+ ]
@@ -0,0 +1,292 @@
1
+ import os
2
+
3
+ from calagoapi.account.models import ApiKey, SshKey, SecurityKey, Session, OAuthLink, CommandSnippet
4
+ from calagoapi.users.activity import Activity
5
+ from calagoapi.utils.model import idOf
6
+
7
+
8
+ class Account:
9
+ '''
10
+ The account that owns the API key, available as `panel.account`.
11
+
12
+ Example:
13
+ print(panel.account.getUsername(), panel.account.getEmail())
14
+ panel.account.addSshKey('laptop', open('~/.ssh/id_ed25519.pub').read())
15
+ for session in panel.account.getSessions():
16
+ print(session.getIP(), session.getUserAgent())
17
+ '''
18
+ def __init__(self, panel):
19
+ self.panel = panel
20
+ self.api = panel.api.account
21
+ self.data = None
22
+
23
+ def __repr__(self):
24
+ return f'<Account {self.getUsername()!r}>' if self.data else '<Account>'
25
+
26
+ # profile
27
+ def getUser(self) -> dict:
28
+ '''Returns the account details (cached, see refresh()).'''
29
+ if self.data is None:
30
+ self.refresh()
31
+ return self.data
32
+
33
+ def refresh(self) -> dict:
34
+ '''Re-fetches the account details.'''
35
+ self.data = self.api.get()
36
+ return self.data
37
+
38
+ def getUUID(self) -> str:
39
+ '''Returns the user UUID.'''
40
+ return self.getUser().get('uuid')
41
+
42
+ def getUsername(self) -> str:
43
+ '''Returns the username.'''
44
+ return self.getUser().get('username')
45
+
46
+ def getEmail(self) -> str:
47
+ '''Returns the email address.'''
48
+ return self.getUser().get('email')
49
+
50
+ def getName(self) -> str:
51
+ '''Returns "First Last", or the username if no name is set.'''
52
+ user = self.getUser()
53
+ return ' '.join(filter(None, [user.get('name_first'), user.get('name_last')])) or user.get('username')
54
+
55
+ def getLanguage(self) -> str:
56
+ '''Returns the language code.'''
57
+ return self.getUser().get('language')
58
+
59
+ def getRole(self) -> dict:
60
+ '''Returns the account role, or None.'''
61
+ return self.getUser().get('role')
62
+
63
+ def getTwoFactorMethods(self) -> list:
64
+ '''Returns enabled two-factor methods: totp, security_key, email.'''
65
+ return self.getUser().get('two_factor_methods', [])
66
+
67
+ def isAdmin(self) -> bool:
68
+ '''Returns True for root administrators.'''
69
+ return bool(self.getUser().get('admin'))
70
+
71
+ def hasTwoFactor(self) -> bool:
72
+ '''Returns True if any two-factor method is enabled.'''
73
+ return bool(self.getTwoFactorMethods())
74
+
75
+ def isEmailVerified(self) -> bool:
76
+ '''Returns True if the email address is verified.'''
77
+ return bool(self.getUser().get('email_verified'))
78
+
79
+ def update(self, username: str = None, name_first: str = None, name_last: str = None, language: str = None) -> None:
80
+ '''Changes profile fields. Only the given fields change.'''
81
+ self.api.update(username, name_first, name_last, language)
82
+ self.data = None
83
+
84
+ def setUsername(self, username: str) -> None:
85
+ '''Changes the username.'''
86
+ return self.update(username=username)
87
+
88
+ def setName(self, name_first: str, name_last: str = None) -> None:
89
+ '''Changes the first and last name.'''
90
+ return self.update(name_first=name_first, name_last=name_last)
91
+
92
+ def setLanguage(self, language: str) -> None:
93
+ '''Changes the language, e.g. "en" or "de".'''
94
+ return self.update(language=language)
95
+
96
+ def changeEmail(self, email: str, password: str) -> dict:
97
+ '''Starts an email change. The new address must be verified.'''
98
+ return self.api.changeEmail(email, password)
99
+
100
+ def resendVerificationEmail(self) -> dict:
101
+ '''Sends the verification email again.'''
102
+ return self.api.resendVerification()
103
+
104
+ def changePassword(self, password: str, new_password: str) -> None:
105
+ '''Changes the password.'''
106
+ return self.api.changePassword(password, new_password)
107
+
108
+ def setPasswordLogin(self, enabled: bool, password: str) -> None:
109
+ '''Allows or forbids logging in with a password (for OAuth/passkey-only accounts).'''
110
+ return self.api.setPasswordLogin(not enabled, password)
111
+
112
+ def setAvatar(self, image, content_type: str = 'image/png') -> str:
113
+ '''
114
+ Uploads a new avatar.
115
+
116
+ Args:
117
+ image: Local file path or image bytes.
118
+
119
+ Returns:
120
+ The avatar URL.
121
+ '''
122
+ if isinstance(image, (str, os.PathLike)):
123
+ with open(image, 'rb') as f:
124
+ image = f.read()
125
+ return self.api.setAvatar(image, content_type)
126
+
127
+ def removeAvatar(self) -> None:
128
+ '''Removes the avatar.'''
129
+ return self.api.deleteAvatar()
130
+
131
+ def getSettings(self) -> dict:
132
+ '''Returns personal UI settings.'''
133
+ return self.api.settings()
134
+
135
+ def updateSettings(self, settings: dict) -> None:
136
+ '''Saves personal UI settings.'''
137
+ return self.api.updateSettings(settings)
138
+
139
+ def getPermissions(self) -> dict:
140
+ '''
141
+ Returns every permission that exists, grouped into user, admin and server permissions.
142
+
143
+ Useful when creating API keys, roles or subusers.
144
+ '''
145
+ return self.api.permissions()
146
+
147
+ def getActivity(self, search: str = None, page: int = 1, per_page: int = 50):
148
+ '''Returns the account activity log.'''
149
+ return self.api.activity(search, page, per_page).map(lambda data: Activity(self.panel, data))
150
+
151
+ def logout(self) -> None:
152
+ '''Ends the current session.'''
153
+ return self.api.logout()
154
+
155
+ # two factor
156
+ def getTwoFactorSetup(self) -> dict:
157
+ '''Returns the TOTP secret and otpauth:// URL for setting up an authenticator app.'''
158
+ return self.api.twoFactorSetup()
159
+
160
+ def enableTwoFactor(self, code: str, password: str) -> list:
161
+ '''Enables TOTP two-factor. Returns recovery codes.'''
162
+ return self.api.enableTwoFactor(code, password)
163
+
164
+ def disableTwoFactor(self, code: str, password: str) -> None:
165
+ '''Disables TOTP two-factor.'''
166
+ return self.api.disableTwoFactor(code, password)
167
+
168
+ def enableEmailTwoFactor(self, password: str) -> list:
169
+ '''Enables email two-factor. Returns recovery codes.'''
170
+ return self.api.enableEmailTwoFactor(password)
171
+
172
+ def disableEmailTwoFactor(self, password: str) -> None:
173
+ '''Disables email two-factor.'''
174
+ return self.api.disableEmailTwoFactor(password)
175
+
176
+ # api keys
177
+ def getApiKeys(self, search: str = None, page: int = None, per_page: int = None):
178
+ '''Lists the account's API keys.'''
179
+ return self.api.apiKeys(search, page, per_page).map(lambda data: ApiKey(self.panel, data))
180
+
181
+ def getApiKey(self, identifier: str) -> ApiKey:
182
+ '''Returns an API key by its identifier (the start of the key).'''
183
+ return ApiKey(self.panel, self.api.apiKeyByIdentifier(identifier))
184
+
185
+ def createApiKey(self, name: str, user_permissions: list = None, server_permissions: list = None,
186
+ admin_permissions: list = None, allowed_ips: list = None, expires: str = None) -> ApiKey:
187
+ '''
188
+ Creates an API key.
189
+
190
+ Args:
191
+ name: Key name.
192
+ user_permissions: Account permissions.
193
+ server_permissions: Server permissions, e.g. ['control.*', 'file.read'].
194
+ admin_permissions: Admin permissions.
195
+ allowed_ips: Restrict usage to these IPs/CIDRs.
196
+ expires: ISO-8601 expiry time.
197
+
198
+ Returns:
199
+ The ApiKey. Call `getKey()` to read the secret, it is shown only once.
200
+ '''
201
+ result = self.api.createApiKey(name, allowed_ips, user_permissions, admin_permissions, server_permissions, expires=expires)
202
+ return ApiKey(self.panel, result['api_key'], result.get('key'))
203
+
204
+ def deleteApiKey(self, api_key) -> None:
205
+ '''Deletes an API key.'''
206
+ return self.api.deleteApiKey(idOf(api_key))
207
+
208
+ # ssh keys
209
+ def getSshKeys(self, search: str = None, page: int = None, per_page: int = None):
210
+ '''Lists SSH keys.'''
211
+ return self.api.sshKeys(search, page, per_page).map(lambda data: SshKey(self.panel, data))
212
+
213
+ def addSshKey(self, name: str, public_key: str) -> SshKey:
214
+ '''Adds an SSH public key for SFTP.'''
215
+ return SshKey(self.panel, self.api.createSshKey(name, public_key))
216
+
217
+ def importSshKeys(self, provider: str, username: str) -> list:
218
+ '''
219
+ Imports public keys from github, gitlab or launchpad.
220
+
221
+ Example:
222
+ panel.account.importSshKeys('github', 'octocat')
223
+ '''
224
+ return [SshKey(self.panel, data) for data in self.api.importSshKeys(provider, username)]
225
+
226
+ def deleteSshKey(self, ssh_key) -> None:
227
+ '''Deletes an SSH key.'''
228
+ return self.api.deleteSshKey(idOf(ssh_key))
229
+
230
+ # security keys
231
+ def getSecurityKeys(self, search: str = None, page: int = None, per_page: int = None):
232
+ '''Lists security keys / passkeys.'''
233
+ return self.api.securityKeys(search, page, per_page).map(lambda data: SecurityKey(self.panel, data))
234
+
235
+ def startSecurityKeyRegistration(self, name: str, password: str, allow_usernameless_login: bool = None) -> dict:
236
+ '''
237
+ Starts registering a security key.
238
+
239
+ Returns:
240
+ dict with the pending `security_key` and WebAuthn `options` to pass to the authenticator.
241
+ Finish with `finishSecurityKeyRegistration()`.
242
+ '''
243
+ return self.api.createSecurityKey(name, password, allow_usernameless_login)
244
+
245
+ def finishSecurityKeyRegistration(self, security_key, public_key_credential: dict) -> str:
246
+ '''Completes a security key registration with the authenticator's response.'''
247
+ key_id = security_key.get('uuid') if isinstance(security_key, dict) else idOf(security_key)
248
+ return self.api.challengeSecurityKey(key_id, public_key_credential)
249
+
250
+ def deleteSecurityKey(self, security_key, password: str = None) -> None:
251
+ '''Deletes a security key.'''
252
+ return self.api.deleteSecurityKey(idOf(security_key), password)
253
+
254
+ # sessions
255
+ def getSessions(self, search: str = None, page: int = None, per_page: int = None):
256
+ '''Lists active browser sessions.'''
257
+ return self.api.sessions(search, page, per_page).map(lambda data: Session(self.panel, data))
258
+
259
+ def logoutOtherSessions(self) -> int:
260
+ '''Logs out every other session. Returns how many were removed.'''
261
+ return self.api.deleteOtherSessions()
262
+
263
+ # oauth
264
+ def getOAuthLinks(self, page: int = None, per_page: int = None):
265
+ '''Lists linked external accounts.'''
266
+ return self.api.oauthLinks(page, per_page).map(lambda data: OAuthLink(self.panel, data))
267
+
268
+ def getOAuthLink(self, oauth_link_id: str) -> OAuthLink:
269
+ '''Returns one linked external account.'''
270
+ return OAuthLink(self.panel, self.api.oauthLink(oauth_link_id))
271
+
272
+ def unlinkOAuth(self, oauth_link) -> None:
273
+ '''Removes a linked external account.'''
274
+ return self.api.deleteOauthLink(idOf(oauth_link))
275
+
276
+ # command snippets
277
+ def getCommandSnippets(self, search: str = None, page: int = None, per_page: int = None):
278
+ '''Lists saved command snippets.'''
279
+ return self.api.commandSnippets(search, page, per_page).map(lambda data: CommandSnippet(self.panel, data))
280
+
281
+ def createCommandSnippet(self, name: str, command: str, eggs: list = None) -> CommandSnippet:
282
+ '''
283
+ Saves a console command.
284
+
285
+ Args:
286
+ eggs: Eggs (UUIDs) the snippet is shown for. All when empty.
287
+ '''
288
+ return CommandSnippet(self.panel, self.api.createCommandSnippet(name, command, [idOf(e) for e in eggs or []]))
289
+
290
+ def deleteCommandSnippet(self, snippet) -> None:
291
+ '''Deletes a command snippet.'''
292
+ return self.api.deleteCommandSnippet(idOf(snippet))
@@ -0,0 +1,72 @@
1
+ class Auth:
2
+ '''
3
+ Public authentication endpoints, available as `panel.auth`.
4
+
5
+ These do not need an API key. They are useful for account tooling such as
6
+ registration bots or password reset helpers.
7
+
8
+ Example:
9
+ panel.auth.forgotPassword('user@example.com')
10
+ for provider in panel.auth.getOAuthProviders():
11
+ print(provider['name'], panel.auth.getOAuthLoginUrl(provider))
12
+ '''
13
+ def __init__(self, panel):
14
+ self.panel = panel
15
+ self.api = panel.api.auth
16
+
17
+ def login(self, user: str, password: str, captcha: str = None) -> dict:
18
+ '''
19
+ Logs in with a username/email and password.
20
+
21
+ Returns:
22
+ dict with `type` 'completed', or 'two_factor_required' together with
23
+ a `token` for `completeTwoFactor()`.
24
+ '''
25
+ return self.api.login(user, password, captcha)
26
+
27
+ def completeTwoFactor(self, code: str, confirmation_token: str, method: str = None) -> dict:
28
+ '''Finishes a login that requires two-factor authentication.'''
29
+ return self.api.checkpoint(code, confirmation_token, method)
30
+
31
+ def sendTwoFactorEmail(self, confirmation_token: str) -> None:
32
+ '''Sends the email two-factor code for a pending login.'''
33
+ return self.api.checkpointEmail(confirmation_token)
34
+
35
+ def register(self, username: str, email: str, password: str, name_first: str = None, name_last: str = None,
36
+ language: str = None, captcha: str = None) -> dict:
37
+ '''Registers a new account (if registration is enabled on the panel).'''
38
+ return self.api.register(username, email, password, name_first, name_last, language, captcha)
39
+
40
+ def verifyEmail(self, token: str) -> dict:
41
+ '''Verifies an email address with the token from the verification email.'''
42
+ return self.api.verifyEmail(token)
43
+
44
+ def forgotPassword(self, email: str, captcha: str = None) -> None:
45
+ '''Sends a password reset email.'''
46
+ return self.api.forgotPassword(email, captcha)
47
+
48
+ def resetPassword(self, token: str, new_password: str) -> None:
49
+ '''Sets a new password using the token from the reset email.'''
50
+ return self.api.resetPassword(token, new_password)
51
+
52
+ def getOAuthProviders(self) -> list:
53
+ '''Returns the OAuth providers users can log in with.'''
54
+ return self.api.oauthProviders()
55
+
56
+ def getOAuthLoginUrl(self, provider) -> str:
57
+ '''Returns the URL that starts an OAuth login with a provider (dict or UUID).'''
58
+ provider_id = provider.get('uuid') if isinstance(provider, dict) else provider
59
+ return self.api.oauthRedirectUrl(provider_id)
60
+
61
+ def getSecurityKeyOptions(self, user: str = None) -> dict:
62
+ '''
63
+ Starts a passkey login.
64
+
65
+ Args:
66
+ user: Username for a username-first login. Omit for discoverable (usernameless) passkeys.
67
+ '''
68
+ return self.api.securityKeyOptions(user, discoverable=user is None)
69
+
70
+ def loginWithSecurityKey(self, uuid: str, public_key_credential: dict, discoverable: bool = False) -> dict:
71
+ '''Completes a passkey login with the authenticator's response.'''
72
+ return self.api.securityKeyLogin(uuid, public_key_credential, discoverable)