-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathPassageCurrentUser.kt
270 lines (257 loc) · 11.2 KB
/
PassageCurrentUser.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
package id.passage.android
import android.app.Activity
import id.passage.android.api.CurrentuserAPI
import id.passage.android.exceptions.AddDevicePasskeyException
import id.passage.android.exceptions.GetMetadataException
import id.passage.android.exceptions.PassageUserException
import id.passage.android.exceptions.UpdateMetadataException
import id.passage.android.exceptions.UserInfoException
import id.passage.android.model.AddDeviceFinishRequest
import id.passage.android.model.AuthenticatorAttachment
import id.passage.android.model.CurrentUserDevicesStartRequest
import id.passage.android.model.MagicLink
import id.passage.android.model.UpdateDeviceRequest
import id.passage.android.model.UpdateMetadataRequest
import id.passage.android.model.UpdateUserEmailRequest
import id.passage.android.model.UpdateUserPhoneRequest
import id.passage.android.model.UserSocialConnections
import id.passage.android.utils.CurrentUserInfo
import id.passage.android.utils.HostedUtils
import id.passage.android.utils.Metadata
import id.passage.android.utils.Passkey
import id.passage.android.utils.PasskeyCreationOptions
import id.passage.android.utils.PasskeyUtils
import id.passage.android.utils.SocialConnection
import okhttp3.OkHttpClient
class PassageCurrentUser(
private val tokenStore: PassageTokenStore,
private val activity: Activity,
private val passageClient: OkHttpClient,
) {
/**
* Get Current User
*
* Returns an instance of PassageUser, which represents an authenticated Passage user.
* The PassageUser class has methods that can be used to retrieve data on the current user
* which require authentication.
* @return PassageUser?
* @throws PassageUserException
*/
suspend fun userInfo(): CurrentUserInfo {
val currentUserAPI = CurrentuserAPI(PassageClientService.basePath, passageClient)
try {
return currentUserAPI.getCurrentuser(Passage.appId).user
} catch (e: Exception) {
throw UserInfoException.convert(e)
}
}
/**
* Change Email
*
* Initiate an email change for the authenticated user. An email change requires verification, so an email will be sent to the user which they must verify before the email change takes effect.
* @param newEmail valid email
* @param language optional language string for localizing emails, if no language or an invalid language is provided the application default language will be used
* @return MagicLink?
* @throws PassageUserException
*/
suspend fun changeEmail(
newEmail: String,
language: String? = null,
): MagicLink {
val currentUserAPI = CurrentuserAPI(PassageClientService.basePath, passageClient)
val request = UpdateUserEmailRequest(newEmail, language, null, null)
val response =
try {
currentUserAPI.updateEmailCurrentuser(Passage.appId, request)
} catch (e: Exception) {
throw PassageUserException.convert(e)
}
return response.magicLink
}
/**
* Change Phone
*
* Initiate a phone number change for the authenticated user. An phone number change requires verification, so an SMS with a link will be sent to the user which they must verify before the phone number change takes effect.
* @param newPhone valid E164 phone number
* @param language optional language string for localizing emails, if no language or an invalid language is provided the application default language will be used
* @return MagicLink?
* @throws PassageUserException
*/
suspend fun changePhone(
newPhone: String,
language: String? = null,
): MagicLink {
val currentUserAPI = CurrentuserAPI(PassageClientService.basePath, passageClient)
val request = UpdateUserPhoneRequest(language, null, newPhone, null)
val response =
try {
currentUserAPI.updatePhoneCurrentuser(Passage.appId, request)
} catch (e: Exception) {
throw PassageUserException.convert(e)
}
return response.magicLink
}
/**
* List Device Passkeys
*
* List all WebAuthn devices for the authenticated user. User must be authenticated via bearer token.
* @return List<PassageCredential>
* @throws PassageUserException
*/
suspend fun passkeys(): List<Passkey> {
val currentUserAPI = CurrentuserAPI(PassageClientService.basePath, passageClient)
return try {
currentUserAPI.getCurrentuserDevices(Passage.appId).devices
} catch (e: Exception) {
throw PassageUserException.convert(e)
}
}
/**
* Add a Device Passkey
*
* Returns the created device for the user. User must be authenticated via a bearer token.
* @param activity Activity to surface the Credentials Manager prompt within
* @param options optional configuration for passkey creation
* @return PassageCredential
* @throws AddDevicePasskeyException
*/
suspend fun addPasskey(options: PasskeyCreationOptions? = null): Passkey {
try {
val currentUserAPI = CurrentuserAPI(PassageClientService.basePath, passageClient)
// Get Create Credential challenge from Passage
val authenticatorAttachment =
options?.authenticatorAttachment
?: AuthenticatorAttachment.platform
val request = CurrentUserDevicesStartRequest(authenticatorAttachment)
val webauthnStartResponse = currentUserAPI.postCurrentuserAddDeviceStart(Passage.appId, request)
// Use Create Credential challenge to prompt user to create a passkey
val createCredOptionsJson = PasskeyUtils.getCreateCredentialOptionsJson(webauthnStartResponse.handshake)
val createCredResponse = PasskeyUtils.createPasskey(createCredOptionsJson, activity)
// Complete registration
val handshakeResponse = PasskeyUtils.getCreateCredentialHandshakeResponse(createCredResponse)
val finishRequest =
AddDeviceFinishRequest(
handshakeId = webauthnStartResponse.handshake.id,
handshakeResponse = handshakeResponse,
userId = webauthnStartResponse.user?.id ?: "",
)
return currentUserAPI.postCurrentuserAddDeviceFinish(Passage.appId, finishRequest).device
} catch (e: Exception) {
throw AddDevicePasskeyException.convert(e)
}
}
/**
* Delete Device Passkey
*
* Revoke a device by ID for the current user. User must be authenticated via a bearer token.
* @param deviceId Device ID
* @return void
* @throws PassageUserException
*/
suspend fun deletePasskey(passkeyId: String) {
val currentUserAPI = CurrentuserAPI(PassageClientService.basePath, passageClient)
try {
currentUserAPI.deleteCurrentuserDevice(Passage.appId, passkeyId)
} catch (e: Exception) {
PassageUserException.convert(e)
}
}
/**
* Edit Device Passkey Name
*
* Update a device by ID for the current user. Currently the only field that can be updated is
* the friendly name. User must be authenticated via a bearer token.
* @param deviceId Device ID
* @param newDevicePasskeyName Friendly Name
* @return PassageCredential
* @throws PassageUserException
*/
suspend fun editPasskey(
passkeyId: String,
friendlyName: String,
): Passkey {
val currentUserAPI = CurrentuserAPI(PassageClientService.basePath, passageClient)
val request = UpdateDeviceRequest(friendlyName = friendlyName)
return try {
currentUserAPI.updateCurrentuserDevice(Passage.appId, passkeyId, request).device
} catch (e: Exception) {
throw PassageUserException.convert(e)
}
}
/**
* Get Social Connections
*
* Retrieves social connections for the current user. The user must be authenticated via a bearer token.
* @return SocialConnectionsResponse containing the list of social connections
* @throws PassageUserException If an error occurs during the retrieval process
*/
suspend fun socialConnections(): UserSocialConnections {
val currentUserAPI = CurrentuserAPI(PassageClientService.basePath)
try {
return currentUserAPI.getCurrentuserSocialConnections(Passage.appId).socialConnections
} catch (e: Exception) {
throw PassageUserException.convert(e)
}
}
/**
* Delete SocialConnection
*
* Deletes a social connection for the current user. The user must be authenticated via a bearer token.
* @param socialConnectionType The type of social connection to delete
* @throws PassageUserException If an error occurs during the deletion process
*/
suspend fun deleteSocialConnection(socialConnectionType: SocialConnection) {
val currentUserAPI = CurrentuserAPI(PassageClientService.basePath, passageClient)
try {
return currentUserAPI.deleteCurrentuserSocialConnection(Passage.appId, socialConnectionType)
} catch (e: Exception) {
throw PassageUserException.convert(e)
}
}
/**
* Retrieve Current User Metadata
*
* Fetches the metadata associated with the current user. This method requires that the user is authenticated via a bearer token.
* The metadata contains various attributes associated with the user, which can be used for user-specific operations.
* @return Metadata? containing the current user's metadata, null if retrieval fails
*/
suspend fun metadata(): Metadata? {
val currentUserAPI = CurrentuserAPI(PassageClientService.basePath, passageClient)
return try {
currentUserAPI.getCurrentuserMetadata(Passage.appId)
} catch (e: Exception) {
throw GetMetadataException.convert(e)
}
}
/**
* Update User Metadata
*
* Updates the metadata associated with the current user. This method requires that the user is authenticated via a bearer token.
* The `metadata` parameter should contain the new metadata that will be associated with the user.
* @param metadata The Metadata object containing the updated user information
* @return CurrentUserInfo? containing the updated user information, null if the update fails
*/
suspend fun updateMetadata(metadata: Metadata): CurrentUserInfo? {
val currentUserAPI = CurrentuserAPI(PassageClientService.basePath, passageClient)
val request = UpdateMetadataRequest(userMetadata = metadata.userMetadata)
return try {
currentUserAPI.updateCurrentuserMetadata(Passage.appId, request).user
} catch (e: Exception) {
throw UpdateMetadataException.convert(e)
}
}
/**
* Sign Out Current User
*
* If the user is using the Hosted Login feature, a webview will open to log them out and then clear the local token store.
* @return void
*/
suspend fun logout() {
val idToken = tokenStore.idToken
if (idToken != null) {
HostedUtils.logout(activity, idToken)
tokenStore.setIdToken(null)
}
tokenStore.clearAndRevokeTokens()
}
}