-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
auth.ts
248 lines (214 loc) · 9.79 KB
/
auth.ts
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
import { ClientOptions } from "@urql/core"
import { Observable } from "rxjs"
import { Component } from "vue"
import { getI18n } from "~/modules/i18n"
import * as E from "fp-ts/Either"
import { AxiosRequestConfig } from "axios"
import { GQLError } from "~/helpers/backend/GQLClient"
/**
* A common (and required) set of fields that describe a user.
*/
export type HoppUser = {
/** A unique ID identifying the user */
uid: string
/** The name to be displayed as the user's */
displayName: string | null
/** The user's email address */
email: string | null
/** URL to the profile picture of the user */
photoURL: string | null
// Regarding `provider` and `accessToken`:
// The current implementation and use case for these 2 fields are super weird due to legacy.
// Currrently these fields are only basically populated for Github Auth as we need the access token issued
// by it to implement Gist submission. I would really love refactor to make this thing more sane.
/** Name of the provider authenticating (NOTE: See notes on `platform/auth.ts`) */
provider?: string
/** Access Token for the auth of the user against the given `provider`. */
accessToken?: string
emailVerified: boolean
}
export type AuthEvent =
| { event: "probable_login"; user: HoppUser } // We have previous login state, but the app is waiting for authentication
| { event: "login"; user: HoppUser } // We are authenticated
| { event: "logout" } // No authentication and we have no previous state
export type GithubSignInResult =
| { type: "success"; user: HoppUser } // The authentication was a success
| { type: "account-exists-with-different-cred"; link: () => Promise<void> } // We authenticated correctly, but the provider didn't match, so we give the user the opportunity to link to continue completing auth
| { type: "error"; err: unknown } // Auth failed completely and we don't know why
export type LoginItemDef = {
id: string
icon: Component
text: (t: ReturnType<typeof getI18n>) => string
onClick: () => Promise<void> | void
}
export type AuthPlatformDef = {
/**
* Returns an observable that emits the current user as per the auth implementation.
*
* NOTES:
* 1. Make sure to emit non-null values once you have credentials to perform backend operations. (Get required tokens ?)
* 2. It is best to let the stream emit a value immediately on subscription (we can do that by basing this on a BehaviourSubject)
*
* @returns An observable which returns a `HoppUser` or null if not logged in (or login not completed)
*/
getCurrentUserStream: () => Observable<HoppUser | null>
/**
* Returns a stream to events happening in the auth mechanism. Common uses these events to
* let subsystems know something is changed by the authentication status and to react accordingly
*
* @returns An observable which emits an AuthEvent over time
*/
getAuthEventsStream: () => Observable<AuthEvent>
/**
* Similar to `getCurrentUserStream` but deals with the authentication being `probable`.
* Probable User for states where, "We haven't authed yet but we are guessing this person will auth eventually".
* This allows for things like Header component to presumpt a state until we auth properly and avoid flashing a "logged out" state.
*
* NOTES:
* 1. It is best to let the stream emit a value immediately on subscription (we can do that by basing this on a BehaviourSubject)
* 2. Once the authentication is confirmed, this stream should emit the same values as `getCurrentUserStream`.
*
* @returns An obsverable which returns a `HoppUser` for the probable user (or confirmed user if authed) or null if we don't know about a probable user
*/
getProbableUserStream: () => Observable<HoppUser | null>
/**
* Returns the currently authed user. (Similar rules apply as `getCurrentUserStream`)
* @returns The authenticated user or null if not logged in
*/
getCurrentUser: () => HoppUser | null
/**
* Returns the most probable to complete auth user. (Similar rules apply as `getProbableUserStream`)
* @returns The probable user or null if have no idea who will auth in
*/
getProbableUser: () => HoppUser | null
/**
* [This is only for Common Init logic to call!]
* Called by Common when it is time to perform initialization activities for authentication.
* (This is the best place to do init work for the auth subsystem in the platform).
*/
performAuthInit: () => void
/**
* Returns the headers that should be applied by the backend GQL API client (see GQLClient)
* inorder to talk to the backend (like apply auth headers ?)
* @returns An object with the header key and header values as strings
*/
getBackendHeaders: () => Record<string, string>
/**
* Called when the backend GQL API client encounters an auth error to check if with the
* current state, if an auth error is possible. This lets the backend GQL client know that
* it can expect an auth error and we should wait and (possibly retry) to re-execute an operation.
* This is useful for cases where queries might fail as the tokens just expired and need to be refreshed,
* so the app can get the new token and GQL client knows to re-execute the same query.
* @returns Whether an error is expected or not
*/
willBackendHaveAuthError: () => boolean
/**
* Used to register a callback where the backend GQL client should reconnect/reconfigure subscriptions
* as some communication parameter changed over time. Like for example, the backend subscription system
* on a id token based mechanism should be let known that the id token has changed and reconnect the subscription
* connection with the updated params.
* @param func The callback function to call
*/
onBackendGQLClientShouldReconnect: (func: () => void) => void
/**
* Called by the platform to provide additional/different config options when
* setting up the URQL based GQLCLient instance
* @returns
*/
getGQLClientOptions?: () => Partial<ClientOptions>
/**
* called by the platform to provide additional/different config options when
* sending requests with axios
* eg: SH needs to include cookies in the request, while Central doesn't and throws a cors error if it does
*
* @returns AxiosRequestConfig
*/
axiosPlatformConfig?: () => AxiosRequestConfig
/**
* Returns the string content that should be returned when the user selects to
* copy auth token from Developer Options.
*
* @returns The auth token (or equivalent) as a string if we have one to give, else null
*/
getDevOptsBackendIDToken: () => string | null
/**
* Returns an empty promise that only resolves when the current probable user because confirmed.
*
* Note:
* 1. Make sure there is a probable user before waiting, as if not, this function will throw
* 2. If the probable user is already confirmed, this function will return an immediately resolved promise
*/
waitProbableLoginToConfirm: () => Promise<void>
/**
* Called to sign in user with email (magic link). This should send backend calls to send the auth email.
* @param email The email that is logging in.
* @returns An empty promise that is resolved when the operation is complete
*/
signInWithEmail: (email: string) => Promise<void>
/**
* Check whether a given link is a valid sign in with email, magic link response url.
* (i.e, a URL that COULD be from a magic link email)
* @param url The url to check
* @returns Whether this is valid or not (NOTE: This is just a structural check not whether this is accepted (hence, not async))
*/
isSignInWithEmailLink: (url: string) => boolean
/**
* Function that validates the magic link redirect and signs in the user
*
* @param email - Email to log in to
* @param url - The action URL which is used to validate login
* @returns A promise that resolves with the user info when auth is completed
*/
signInWithEmailLink: (email: string, url: string) => Promise<void>
/**
* function that validates the magic link & signs the user in
*/
processMagicLink: () => Promise<void>
/**
* Sends email verification email (the checkmark besides the email)
* @returns When the check has succeed and completed
*/
verifyEmailAddress: () => Promise<void>
/**
* Signs user in with Google.
* @returns A promise that resolves with the user info when auth is completed
*/
signInUserWithGoogle: () => Promise<void>
/**
* Signs user in with Github.
* @returns A promise that resolves with the auth status, giving an opportunity to link if or handle failures
*/
signInUserWithGithub: () => Promise<GithubSignInResult> | Promise<undefined>
/**
* Signs user in with Microsoft.
* @returns A promise that resolves with the user info when auth is completed
*/
signInUserWithMicrosoft: () => Promise<void>
/**
* Signs out the user from auth
* @returns An empty promise that is resolved when the operation is complete
*/
signOutUser: () => Promise<void>
/**
* Updates the email address of the user
* @param email The new email to set this to.
* @returns An empty promise that is resolved when the operation is complete
*/
setEmailAddress: (email: string) => Promise<void>
/**
* Updates the display name of the user
* @param name The new name to set this to.
* @returns A promise that resolves with the display name update status when the operation is complete
*/
setDisplayName: (
name: string
) => Promise<E.Either<GQLError<string>, undefined>>
/**
* Returns the list of allowed auth providers for the platform ( the currently supported ones are GOOGLE, GITHUB, EMAIL, MICROSOFT, SAML )
*/
getAllowedAuthProviders: () => Promise<E.Either<string, string[]>>
/**
* Defines the additional login items that should be shown in the login screen
*/
additionalLoginItems?: LoginItemDef[]
}