diff --git a/keyserver/src/responders/landing-handler.js b/keyserver/src/responders/landing-handler.js --- a/keyserver/src/responders/landing-handler.js +++ b/keyserver/src/responders/landing-handler.js @@ -8,6 +8,7 @@ import ReactDOMServer from 'react-dom/server'; import { promisify } from 'util'; +import { type SIWEMessageType } from 'lib/types/siwe-types.js'; import { isValidPrimaryIdentityPublicKey, isValidSIWENonce, @@ -139,17 +140,18 @@ }); return; } - const siweMessageType = req.header('siwe-message-type'); + const siweMessageTypeRawString = req.header('siwe-message-type'); if ( - siweMessageType !== null && - siweMessageType !== undefined && - !isValidSIWEMessageType(siweMessageType) + siweMessageTypeRawString !== null && + siweMessageTypeRawString !== undefined && + !isValidSIWEMessageType(siweMessageTypeRawString) ) { res.status(400).send({ message: 'Invalid siwe message type.', }); return; } + const siweMessageType = ((siweMessageTypeRawString: any): SIWEMessageType); const [{ jsURL, fontURLs, cssInclude }, LandingSSR] = await Promise.all([ getAssetInfo(), diff --git a/landing/landing-ssr.react.js b/landing/landing-ssr.react.js --- a/landing/landing-ssr.react.js +++ b/landing/landing-ssr.react.js @@ -3,6 +3,8 @@ import * as React from 'react'; import { StaticRouter } from 'react-router'; +import { type SIWEMessageType } from 'lib/types/siwe-types.js'; + import Landing from './landing.react.js'; import { SIWEContext } from './siwe-context.js'; @@ -11,7 +13,7 @@ +basename: string, +siweNonce: ?string, +siwePrimaryIdentityPublicKey: ?string, - +siweMessageType: ?string, + +siweMessageType: ?SIWEMessageType, }; function LandingSSR(props: LandingSSRProps): React.Node { const { diff --git a/landing/root.js b/landing/root.js --- a/landing/root.js +++ b/landing/root.js @@ -3,13 +3,15 @@ import * as React from 'react'; import { BrowserRouter } from 'react-router-dom'; +import { type SIWEMessageType } from 'lib/types/siwe-types.js'; + import Landing from './landing.react.js'; import { SIWEContext } from './siwe-context.js'; declare var routerBasename: string; declare var siweNonce: ?string; declare var siwePrimaryIdentityPublicKey: ?string; -declare var siweMessageType: ?string; +declare var siweMessageType: ?SIWEMessageType; function RootComponent(): React.Node { const siweContextValue = React.useMemo( diff --git a/landing/siwe-context.js b/landing/siwe-context.js --- a/landing/siwe-context.js +++ b/landing/siwe-context.js @@ -2,10 +2,12 @@ import * as React from 'react'; +import { type SIWEMessageType } from 'lib/types/siwe-types.js'; + export type SIWEContextType = { +siweNonce: ?string, +siwePrimaryIdentityPublicKey: ?string, - +siweMessageType: ?string, + +siweMessageType: ?SIWEMessageType, }; const SIWEContext: React.Context = React.createContext({ diff --git a/landing/siwe.react.js b/landing/siwe.react.js --- a/landing/siwe.react.js +++ b/landing/siwe.react.js @@ -68,15 +68,23 @@ React.useContext(SIWEContext); const onClick = React.useCallback(() => { invariant(siweNonce, 'nonce must be present during SIWE attempt'); + invariant(siweMessageType, 'message type must be set during SIWE attempt'); invariant( siwePrimaryIdentityPublicKey, 'primaryIdentityPublicKey must be present during SIWE attempt', ); const statement = getSIWEStatementForPublicKey( siwePrimaryIdentityPublicKey, + siweMessageType, ); void signInWithEthereum(address, signer, siweNonce, statement); - }, [address, signer, siweNonce, siwePrimaryIdentityPublicKey]); + }, [ + address, + signer, + siweNonce, + siwePrimaryIdentityPublicKey, + siweMessageType, + ]); const { openConnectModal } = useConnectModal(); const hasNonce = siweNonce !== null && siweNonce !== undefined; diff --git a/lib/utils/siwe-utils.js b/lib/utils/siwe-utils.js --- a/lib/utils/siwe-utils.js +++ b/lib/utils/siwe-utils.js @@ -5,7 +5,11 @@ import t, { type TEnums } from 'tcomb'; import { isDev } from './dev-utils.js'; -import { type SIWEMessage, SIWEMessageTypes } from '../types/siwe-types.js'; +import { + type SIWEMessage, + SIWEMessageTypes, + type SIWEMessageType, +} from '../types/siwe-types.js'; import { values } from '../utils/objects.js'; const siweNonceRegex: RegExp = /^[a-zA-Z0-9]{17}$/; @@ -78,16 +82,25 @@ ); } -function getSIWEStatementForPublicKey(publicKey: string): string { +function getSIWEStatementForPublicKey( + publicKey: string, + messageType: SIWEMessageType, +): string { invariant( isValidPrimaryIdentityPublicKey(publicKey), 'publicKey must be well formed in getSIWEStatementForPublicKey', ); - return `Device IdPubKey: ${publicKey} ${siweStatementLegalAgreement}`; + if (messageType === SIWEMessageTypes.MSG_AUTH) { + return `Primary device IdPubKey: ${publicKey} ${siweStatementLegalAgreement}`; + } + return ( + `Backup message for primary device IdPubKey: ` + + `${publicKey} ${siweStatementLegalAgreement}` + ); } const siweStatementWithPublicKeyRegex = - /^Device IdPubKey: [a-zA-Z0-9+/]{43} By continuing, I accept the Comm Terms of Service: https:\/\/comm.app\/terms$/; + /^(Primary device|Backup message for primary device) IdPubKey: [a-zA-Z0-9+/]{43} By continuing, I accept the Comm Terms of Service: https:\/\/comm.app\/terms$/; function isValidSIWEStatementWithPublicKey(candidate: string): boolean { return siweStatementWithPublicKeyRegex.test(candidate); } diff --git a/lib/utils/siwe-utils.test.js b/lib/utils/siwe-utils.test.js --- a/lib/utils/siwe-utils.test.js +++ b/lib/utils/siwe-utils.test.js @@ -8,6 +8,7 @@ isValidSIWENonce, isValidSIWEStatementWithPublicKey, } from './siwe-utils.js'; +import { SIWEMessageTypes } from '../types/siwe-types.js'; describe('SIWE Nonce utils', () => { it('isValidSIWENonce should match valid nonces', () => { @@ -179,37 +180,79 @@ expect(isValidPrimaryIdentityPublicKey(longPublicKey)).toBe(false); }); - it(`getSIWEStatementForPublicKey should generate expected statement`, () => { + it(`getSIWEStatementForPublicKey should generate expected statements`, () => { const validPublicKey = 'rPFzRtV7E6v1b60zjTvghqb2xgnggmn6j4UaYccJYdo'; - const expectedString = - `Device IdPubKey: rPFzRtV7E6v1b60zjTvghqb2xgnggmn6j4UaYccJYdo ` + + const expectedStringForSocialProof = + `Primary device IdPubKey: rPFzRtV7E6v1b60zjTvghqb2xgnggmn6j4UaYccJYdo ` + `By continuing, I accept the Comm Terms of Service: https://comm.app/terms`; expect( - getSIWEStatementForPublicKey(validPublicKey) === expectedString, + getSIWEStatementForPublicKey( + validPublicKey, + SIWEMessageTypes.MSG_AUTH, + ) === expectedStringForSocialProof, + ).toBe(true); + + const expectedStringForBackupMessage = + `Backup message for primary device IdPubKey: ` + + `rPFzRtV7E6v1b60zjTvghqb2xgnggmn6j4UaYccJYdo ` + + `By continuing, I accept the Comm Terms of Service: https://comm.app/terms`; + expect( + getSIWEStatementForPublicKey( + validPublicKey, + SIWEMessageTypes.MSG_BACKUP, + ) === expectedStringForBackupMessage, ).toBe(true); }); it(`isValidSIWEStatementWithPublicKey should be true for well formed SIWE statement`, () => { const validPublicKey = 'rPFzRtV7E6v1b60zjTvghqb2xgnggmn6j4UaYccJYdo'; - const expectedString = - `Device IdPubKey: rPFzRtV7E6v1b60zjTvghqb2xgnggmn6j4UaYccJYdo ` + + const expectedStringForSocialProof = + `Primary device IdPubKey: rPFzRtV7E6v1b60zjTvghqb2xgnggmn6j4UaYccJYdo ` + + `By continuing, I accept the Comm Terms of Service: https://comm.app/terms`; + + expect( + isValidSIWEStatementWithPublicKey(expectedStringForSocialProof), + ).toBe(true); + expect( + isValidSIWEStatementWithPublicKey( + getSIWEStatementForPublicKey(validPublicKey, SIWEMessageTypes.MSG_AUTH), + ), + ).toBe(true); + + const expectedStringForBackupMessage = + `Backup message for primary device IdPubKey: ` + + `rPFzRtV7E6v1b60zjTvghqb2xgnggmn6j4UaYccJYdo ` + `By continuing, I accept the Comm Terms of Service: https://comm.app/terms`; - expect(isValidSIWEStatementWithPublicKey(expectedString)).toBe(true); + expect( + isValidSIWEStatementWithPublicKey(expectedStringForBackupMessage), + ).toBe(true); expect( isValidSIWEStatementWithPublicKey( - getSIWEStatementForPublicKey(validPublicKey), + getSIWEStatementForPublicKey( + validPublicKey, + SIWEMessageTypes.MSG_BACKUP, + ), ), ).toBe(true); }); - it(`getPublicKeyFromSIWEStatement should pull public key out of valid SIWE statement`, () => { - const validSIWEMessageStatement = - `Device IdPubKey: rPFzRtV7E6v1b60zjTvghqb2xgnggmn6j4UaYccJYdo ` + + it(`getPublicKeyFromSIWEStatement should pull public key out of valid SIWE statements`, () => { + const validSIWEMessageStatementForSocialProof = + `Primary device IdPubKey: rPFzRtV7E6v1b60zjTvghqb2xgnggmn6j4UaYccJYdo ` + `By continuing, I accept the Comm Terms of Service: https://comm.app/terms`; - expect(getPublicKeyFromSIWEStatement(validSIWEMessageStatement)).toBe( - 'rPFzRtV7E6v1b60zjTvghqb2xgnggmn6j4UaYccJYdo', - ); + const validSIWEStatementForBackupMessage = + `Backup message for primary device IdPubKey: ` + + `rPFzRtV7E6v1b60zjTvghqb2xgnggmn6j4UaYccJYdo ` + + `By continuing, I accept the Comm Terms of Service: https://comm.app/terms`; + + expect( + getPublicKeyFromSIWEStatement(validSIWEMessageStatementForSocialProof), + ).toBe('rPFzRtV7E6v1b60zjTvghqb2xgnggmn6j4UaYccJYdo'); + + expect( + getPublicKeyFromSIWEStatement(validSIWEStatementForBackupMessage), + ).toBe('rPFzRtV7E6v1b60zjTvghqb2xgnggmn6j4UaYccJYdo'); }); }); diff --git a/web/account/siwe-login-form.react.js b/web/account/siwe-login-form.react.js --- a/web/account/siwe-login-form.react.js +++ b/web/account/siwe-login-form.react.js @@ -28,6 +28,7 @@ LogInStartingPayload, LogInExtraInfo, } from 'lib/types/account-types.js'; +import { SIWEMessageTypes } from 'lib/types/siwe-types.js'; import { getMessageForException, ServerError } from 'lib/utils/errors.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; @@ -181,7 +182,10 @@ const { primaryIdentityPublicKeys: { ed25519 }, } = await olmAPI.getUserPublicKey(); - const statement = getSIWEStatementForPublicKey(ed25519); + const statement = getSIWEStatementForPublicKey( + ed25519, + SIWEMessageTypes.MSG_AUTH, + ); const message = createSIWEMessage(address, statement, siweNonce); const signature = await signer.signMessage({ message }); if (usingCommServicesAccessToken) {