Changeset View
Changeset View
Standalone View
Standalone View
keyserver/src/responders/user-responders.js
Show All 23 Lines | import type { | ||||
UpdateUserSettingsRequest, | UpdateUserSettingsRequest, | ||||
PolicyAcknowledgmentRequest, | PolicyAcknowledgmentRequest, | ||||
} from 'lib/types/account-types.js'; | } from 'lib/types/account-types.js'; | ||||
import { | import { | ||||
userSettingsTypes, | userSettingsTypes, | ||||
notificationTypeValues, | notificationTypeValues, | ||||
logInActionSources, | logInActionSources, | ||||
} from 'lib/types/account-types.js'; | } from 'lib/types/account-types.js'; | ||||
import type { | import { | ||||
ClientAvatar, | type ClientAvatar, | ||||
UpdateUserAvatarRequest, | clientAvatarValidator, | ||||
UpdateUserAvatarResponse, | type UpdateUserAvatarRequest, | ||||
type UpdateUserAvatarResponse, | |||||
} from 'lib/types/avatar-types.js'; | } from 'lib/types/avatar-types.js'; | ||||
import type { | import type { | ||||
IdentityKeysBlob, | IdentityKeysBlob, | ||||
SignedIdentityKeysBlob, | SignedIdentityKeysBlob, | ||||
} from 'lib/types/crypto-types.js'; | } from 'lib/types/crypto-types.js'; | ||||
import { | import { | ||||
type CalendarQuery, | type CalendarQuery, | ||||
rawEntryInfoValidator, | rawEntryInfoValidator, | ||||
Show All 9 Lines | import type { | ||||
SIWESocialProof, | SIWESocialProof, | ||||
} from 'lib/types/siwe-types.js'; | } from 'lib/types/siwe-types.js'; | ||||
import { | import { | ||||
type SubscriptionUpdateRequest, | type SubscriptionUpdateRequest, | ||||
type SubscriptionUpdateResponse, | type SubscriptionUpdateResponse, | ||||
threadSubscriptionValidator, | threadSubscriptionValidator, | ||||
} from 'lib/types/subscription-types.js'; | } from 'lib/types/subscription-types.js'; | ||||
import { rawThreadInfoValidator } from 'lib/types/thread-types.js'; | import { rawThreadInfoValidator } from 'lib/types/thread-types.js'; | ||||
import { createUpdatesResultValidator } from 'lib/types/update-types.js'; | |||||
import { | import { | ||||
type PasswordUpdate, | type PasswordUpdate, | ||||
loggedOutUserInfoValidator, | loggedOutUserInfoValidator, | ||||
loggedInUserInfoValidator, | loggedInUserInfoValidator, | ||||
oldLoggedInUserInfoValidator, | oldLoggedInUserInfoValidator, | ||||
userInfoValidator, | userInfoValidator, | ||||
} from 'lib/types/user-types.js'; | } from 'lib/types/user-types.js'; | ||||
import { updateUserAvatarRequestValidator } from 'lib/utils/avatar-utils.js'; | import { updateUserAvatarRequestValidator } from 'lib/utils/avatar-utils.js'; | ||||
▲ Show 20 Lines • Show All 55 Lines • ▼ Show 20 Lines | import { | ||||
checkAndSendPasswordResetEmail, | checkAndSendPasswordResetEmail, | ||||
updatePassword, | updatePassword, | ||||
updateUserSettings, | updateUserSettings, | ||||
updateUserAvatar, | updateUserAvatar, | ||||
} from '../updaters/account-updaters.js'; | } from '../updaters/account-updaters.js'; | ||||
import { userSubscriptionUpdater } from '../updaters/user-subscription-updaters.js'; | import { userSubscriptionUpdater } from '../updaters/user-subscription-updaters.js'; | ||||
import { viewerAcknowledgmentUpdater } from '../updaters/viewer-acknowledgment-updater.js'; | import { viewerAcknowledgmentUpdater } from '../updaters/viewer-acknowledgment-updater.js'; | ||||
import { getOlmUtility } from '../utils/olm-utils.js'; | import { getOlmUtility } from '../utils/olm-utils.js'; | ||||
import { validateInput } from '../utils/validation-utils.js'; | import { validateInput, validateOutput } from '../utils/validation-utils.js'; | ||||
const subscriptionUpdateRequestInputValidator = tShape({ | const subscriptionUpdateRequestInputValidator = tShape({ | ||||
threadID: t.String, | threadID: t.String, | ||||
updatedFields: tShape({ | updatedFields: tShape({ | ||||
pushNotifs: t.maybe(t.Boolean), | pushNotifs: t.maybe(t.Boolean), | ||||
home: t.maybe(t.Boolean), | home: t.maybe(t.Boolean), | ||||
}), | }), | ||||
}); | }); | ||||
export const subscriptionUpdateResponseValidator: TInterface<SubscriptionUpdateResponse> = | export const subscriptionUpdateResponseValidator: TInterface<SubscriptionUpdateResponse> = | ||||
tShape<SubscriptionUpdateResponse>({ | tShape<SubscriptionUpdateResponse>({ | ||||
threadSubscription: threadSubscriptionValidator, | threadSubscription: threadSubscriptionValidator, | ||||
}); | }); | ||||
async function userSubscriptionUpdateResponder( | async function userSubscriptionUpdateResponder( | ||||
viewer: Viewer, | viewer: Viewer, | ||||
input: any, | input: any, | ||||
): Promise<SubscriptionUpdateResponse> { | ): Promise<SubscriptionUpdateResponse> { | ||||
const request: SubscriptionUpdateRequest = input; | const request: SubscriptionUpdateRequest = input; | ||||
await validateInput(viewer, subscriptionUpdateRequestInputValidator, request); | await validateInput(viewer, subscriptionUpdateRequestInputValidator, request); | ||||
const threadSubscription = await userSubscriptionUpdater(viewer, request); | const threadSubscription = await userSubscriptionUpdater(viewer, request); | ||||
return { threadSubscription }; | return validateOutput(viewer, subscriptionUpdateResponseValidator, { | ||||
threadSubscription, | |||||
}); | |||||
} | } | ||||
const accountUpdateInputValidator = tShape({ | const accountUpdateInputValidator = tShape({ | ||||
updatedFields: tShape({ | updatedFields: tShape({ | ||||
email: t.maybe(tEmail), | email: t.maybe(tEmail), | ||||
password: t.maybe(tPassword), | password: t.maybe(tPassword), | ||||
}), | }), | ||||
currentPassword: tPassword, | currentPassword: tPassword, | ||||
Show All 38 Lines | const [anonymousViewerData] = await Promise.all([ | ||||
createNewAnonymousCookie({ | createNewAnonymousCookie({ | ||||
platformDetails: viewer.platformDetails, | platformDetails: viewer.platformDetails, | ||||
deviceToken: viewer.deviceToken, | deviceToken: viewer.deviceToken, | ||||
}), | }), | ||||
deleteCookie(viewer.cookieID), | deleteCookie(viewer.cookieID), | ||||
]); | ]); | ||||
viewer.setNewCookie(anonymousViewerData); | viewer.setNewCookie(anonymousViewerData); | ||||
} | } | ||||
return { | const response = { | ||||
currentUserInfo: { | currentUserInfo: { | ||||
id: viewer.id, | id: viewer.id, | ||||
anonymous: true, | anonymous: true, | ||||
}, | }, | ||||
}; | }; | ||||
return validateOutput(viewer, logOutResponseValidator, response); | |||||
} | } | ||||
const deleteAccountRequestInputValidator = tShape({ | const deleteAccountRequestInputValidator = tShape({ | ||||
password: t.maybe(tPassword), | password: t.maybe(tPassword), | ||||
}); | }); | ||||
async function accountDeletionResponder( | async function accountDeletionResponder( | ||||
viewer: Viewer, | viewer: Viewer, | ||||
input: any, | input: any, | ||||
): Promise<LogOutResponse> { | ): Promise<LogOutResponse> { | ||||
const request: DeleteAccountRequest = input; | const request: DeleteAccountRequest = input; | ||||
await validateInput(viewer, deleteAccountRequestInputValidator, request); | await validateInput(viewer, deleteAccountRequestInputValidator, request); | ||||
const result = await deleteAccount(viewer, request); | const result = await deleteAccount(viewer, request); | ||||
invariant(result, 'deleteAccount should return result if handed request'); | invariant(result, 'deleteAccount should return result if handed request'); | ||||
return result; | return validateOutput(viewer, logOutResponseValidator, result); | ||||
} | } | ||||
const deviceTokenUpdateRequestInputValidator = tShape({ | const deviceTokenUpdateRequestInputValidator = tShape({ | ||||
deviceType: t.maybe(t.enums.of(['ios', 'android'])), | deviceType: t.maybe(t.enums.of(['ios', 'android'])), | ||||
deviceToken: t.String, | deviceToken: t.String, | ||||
}); | }); | ||||
const registerRequestInputValidator = tShape({ | const registerRequestInputValidator = tShape({ | ||||
▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | try { | ||||
identityKeys.primaryIdentityPublicKeys.ed25519, | identityKeys.primaryIdentityPublicKeys.ed25519, | ||||
signedIdentityKeysBlob.payload, | signedIdentityKeysBlob.payload, | ||||
signedIdentityKeysBlob.signature, | signedIdentityKeysBlob.signature, | ||||
); | ); | ||||
} catch (e) { | } catch (e) { | ||||
throw new ServerError('invalid_signature'); | throw new ServerError('invalid_signature'); | ||||
} | } | ||||
} | } | ||||
return await createAccount(viewer, request); | const response = await createAccount(viewer, request); | ||||
return validateOutput(viewer, registerResponseValidator, response); | |||||
} | } | ||||
type ProcessSuccessfulLoginParams = { | type ProcessSuccessfulLoginParams = { | ||||
+viewer: Viewer, | +viewer: Viewer, | ||||
+input: any, | +input: any, | ||||
+userID: string, | +userID: string, | ||||
+calendarQuery: ?CalendarQuery, | +calendarQuery: ?CalendarQuery, | ||||
+socialProof?: ?SIWESocialProof, | +socialProof?: ?SIWESocialProof, | ||||
▲ Show 20 Lines • Show All 188 Lines • ▼ Show 20 Lines | ): Promise<LogInResponse> { | ||||
const userRow = userResult[0]; | const userRow = userResult[0]; | ||||
if (!userRow.hash || !bcrypt.compareSync(request.password, userRow.hash)) { | if (!userRow.hash || !bcrypt.compareSync(request.password, userRow.hash)) { | ||||
throw new ServerError('invalid_credentials'); | throw new ServerError('invalid_credentials'); | ||||
} | } | ||||
const id = userRow.id.toString(); | const id = userRow.id.toString(); | ||||
return await processSuccessfulLogin({ | const response = await processSuccessfulLogin({ | ||||
viewer, | viewer, | ||||
input, | input, | ||||
userID: id, | userID: id, | ||||
calendarQuery, | calendarQuery, | ||||
signedIdentityKeysBlob, | signedIdentityKeysBlob, | ||||
}); | }); | ||||
return validateOutput(viewer, logInResponseValidator, response); | |||||
} | } | ||||
const siweAuthRequestInputValidator = tShape({ | const siweAuthRequestInputValidator = tShape({ | ||||
signature: t.String, | signature: t.String, | ||||
message: t.String, | message: t.String, | ||||
calendarQuery: entryQueryInputValidator, | calendarQuery: entryQueryInputValidator, | ||||
deviceTokenUpdateRequest: t.maybe(deviceTokenUpdateRequestInputValidator), | deviceTokenUpdateRequest: t.maybe(deviceTokenUpdateRequestInputValidator), | ||||
platformDetails: tPlatformDetails, | platformDetails: tPlatformDetails, | ||||
▲ Show 20 Lines • Show All 111 Lines • ▼ Show 20 Lines | if (!userID) { | ||||
}; | }; | ||||
userID = await processSIWEAccountCreation( | userID = await processSIWEAccountCreation( | ||||
viewer, | viewer, | ||||
siweAccountCreationRequest, | siweAccountCreationRequest, | ||||
); | ); | ||||
} | } | ||||
// 9. Complete login with call to `processSuccessfulLogin(...)`. | // 9. Complete login with call to `processSuccessfulLogin(...)`. | ||||
return await processSuccessfulLogin({ | const response = await processSuccessfulLogin({ | ||||
viewer, | viewer, | ||||
input, | input, | ||||
userID, | userID, | ||||
calendarQuery, | calendarQuery, | ||||
socialProof, | socialProof, | ||||
signedIdentityKeysBlob, | signedIdentityKeysBlob, | ||||
}); | }); | ||||
return validateOutput(viewer, logInResponseValidator, response); | |||||
} | } | ||||
const updatePasswordRequestInputValidator = tShape({ | const updatePasswordRequestInputValidator = tShape({ | ||||
code: t.String, | code: t.String, | ||||
password: tPassword, | password: tPassword, | ||||
watchedIDs: t.list(t.String), | watchedIDs: t.list(t.String), | ||||
calendarQuery: t.maybe(entryQueryInputValidator), | calendarQuery: t.maybe(entryQueryInputValidator), | ||||
deviceTokenUpdateRequest: t.maybe(deviceTokenUpdateRequestInputValidator), | deviceTokenUpdateRequest: t.maybe(deviceTokenUpdateRequestInputValidator), | ||||
platformDetails: tPlatformDetails, | platformDetails: tPlatformDetails, | ||||
}); | }); | ||||
async function oldPasswordUpdateResponder( | async function oldPasswordUpdateResponder( | ||||
viewer: Viewer, | viewer: Viewer, | ||||
input: any, | input: any, | ||||
): Promise<LogInResponse> { | ): Promise<LogInResponse> { | ||||
await validateInput(viewer, updatePasswordRequestInputValidator, input); | await validateInput(viewer, updatePasswordRequestInputValidator, input); | ||||
const request: UpdatePasswordRequest = input; | const request: UpdatePasswordRequest = input; | ||||
if (request.calendarQuery) { | if (request.calendarQuery) { | ||||
request.calendarQuery = normalizeCalendarQuery(request.calendarQuery); | request.calendarQuery = normalizeCalendarQuery(request.calendarQuery); | ||||
} | } | ||||
return await updatePassword(viewer, request); | const response = await updatePassword(viewer, request); | ||||
return validateOutput(viewer, logInResponseValidator, response); | |||||
} | } | ||||
const updateUserSettingsInputValidator = tShape({ | const updateUserSettingsInputValidator = tShape({ | ||||
name: t.irreducible( | name: t.irreducible( | ||||
userSettingsTypes.DEFAULT_NOTIFICATIONS, | userSettingsTypes.DEFAULT_NOTIFICATIONS, | ||||
x => x === userSettingsTypes.DEFAULT_NOTIFICATIONS, | x => x === userSettingsTypes.DEFAULT_NOTIFICATIONS, | ||||
), | ), | ||||
data: t.enums.of(notificationTypeValues), | data: t.enums.of(notificationTypeValues), | ||||
}); | }); | ||||
async function updateUserSettingsResponder( | async function updateUserSettingsResponder( | ||||
viewer: Viewer, | viewer: Viewer, | ||||
input: any, | input: any, | ||||
): Promise<void> { | ): Promise<void> { | ||||
const request: UpdateUserSettingsRequest = input; | const request: UpdateUserSettingsRequest = input; | ||||
await validateInput(viewer, updateUserSettingsInputValidator, request); | await validateInput(viewer, updateUserSettingsInputValidator, request); | ||||
return await updateUserSettings(viewer, request); | await updateUserSettings(viewer, request); | ||||
} | } | ||||
const policyAcknowledgmentRequestInputValidator = tShape({ | const policyAcknowledgmentRequestInputValidator = tShape({ | ||||
policy: t.maybe(t.enums.of(policies)), | policy: t.maybe(t.enums.of(policies)), | ||||
}); | }); | ||||
async function policyAcknowledgmentResponder( | async function policyAcknowledgmentResponder( | ||||
viewer: Viewer, | viewer: Viewer, | ||||
input: any, | input: any, | ||||
): Promise<void> { | ): Promise<void> { | ||||
const request: PolicyAcknowledgmentRequest = input; | const request: PolicyAcknowledgmentRequest = input; | ||||
await validateInput( | await validateInput( | ||||
viewer, | viewer, | ||||
policyAcknowledgmentRequestInputValidator, | policyAcknowledgmentRequestInputValidator, | ||||
request, | request, | ||||
); | ); | ||||
await viewerAcknowledgmentUpdater(viewer, request.policy); | await viewerAcknowledgmentUpdater(viewer, request.policy); | ||||
} | } | ||||
export const updateUserAvatarResponseValidator: TInterface<UpdateUserAvatarResponse> = | |||||
tShape<UpdateUserAvatarResponse>({ | |||||
updates: createUpdatesResultValidator, | |||||
}); | |||||
const updateUserAvatarResponderValidator = t.union([ | |||||
t.maybe(clientAvatarValidator), | |||||
updateUserAvatarResponseValidator, | |||||
]); | |||||
async function updateUserAvatarResponder( | async function updateUserAvatarResponder( | ||||
viewer: Viewer, | viewer: Viewer, | ||||
input: any, | input: any, | ||||
): Promise<?ClientAvatar | UpdateUserAvatarResponse> { | ): Promise<?ClientAvatar | UpdateUserAvatarResponse> { | ||||
const request: UpdateUserAvatarRequest = input; | const request: UpdateUserAvatarRequest = input; | ||||
await validateInput(viewer, updateUserAvatarRequestValidator, request); | await validateInput(viewer, updateUserAvatarRequestValidator, request); | ||||
return await updateUserAvatar(viewer, request); | const result = await updateUserAvatar(viewer, request); | ||||
return validateOutput(viewer, updateUserAvatarResponderValidator, result); | |||||
} | } | ||||
export { | export { | ||||
userSubscriptionUpdateResponder, | userSubscriptionUpdateResponder, | ||||
passwordUpdateResponder, | passwordUpdateResponder, | ||||
sendVerificationEmailResponder, | sendVerificationEmailResponder, | ||||
sendPasswordResetEmailResponder, | sendPasswordResetEmailResponder, | ||||
logOutResponder, | logOutResponder, | ||||
Show All 9 Lines |