Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Merge pull request #3256 from matrix-org/jryans/is-v2-auth
Browse files Browse the repository at this point in the history
Add support for IS v2 API with authentication
  • Loading branch information
jryans authored Jul 30, 2019
2 parents 8b6c70f + 92dbb58 commit a9a33f5
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 44 deletions.
36 changes: 21 additions & 15 deletions src/AddThreepid.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.

import MatrixClientPeg from './MatrixClientPeg';
import { _t } from './languageHandler';
import IdentityAuthClient from './IdentityAuthClient';

/**
* Allows a user to add a third party identifier to their homeserver and,
Expand Down Expand Up @@ -103,24 +104,29 @@ export default class AddThreepid {
/**
* Takes a phone number verification code as entered by the user and validates
* it with the ID server, then if successful, adds the phone number.
* @param {string} token phone number verification code as entered by the user
* @param {string} msisdnToken phone number verification code as entered by the user
* @return {Promise} Resolves if the phone number was added. Rejects with an object
* with a "message" property which contains a human-readable message detailing why
* the request failed.
*/
haveMsisdnToken(token) {
return MatrixClientPeg.get().submitMsisdnToken(
this.sessionId, this.clientSecret, token,
).then((result) => {
if (result.errcode) {
throw result;
}
const identityServerDomain = MatrixClientPeg.get().idBaseUrl.split("://")[1];
return MatrixClientPeg.get().addThreePid({
sid: this.sessionId,
client_secret: this.clientSecret,
id_server: identityServerDomain,
}, this.bind);
});
async haveMsisdnToken(msisdnToken) {
const authClient = new IdentityAuthClient();
const identityAccessToken = await authClient.getAccessToken();
const result = await MatrixClientPeg.get().submitMsisdnToken(
this.sessionId,
this.clientSecret,
msisdnToken,
identityAccessToken,
);
if (result.errcode) {
throw result;
}

const identityServerDomain = MatrixClientPeg.get().idBaseUrl.split("://")[1];
return MatrixClientPeg.get().addThreePid({
sid: this.sessionId,
client_secret: this.clientSecret,
id_server: identityServerDomain,
}, this.bind);
}
}
92 changes: 92 additions & 0 deletions src/IdentityAuthClient.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import MatrixClientPeg from './MatrixClientPeg';

export default class IdentityAuthClient {
constructor() {
this.accessToken = null;
this.authEnabled = true;
}

hasCredentials() {
return this.accessToken != null; // undef or null
}

// Returns a promise that resolves to the access_token string from the IS
async getAccessToken() {
if (!this.authEnabled) {
// The current IS doesn't support authentication
return null;
}

let token = this.accessToken;
if (!token) {
token = window.localStorage.getItem("mx_is_access_token");
}

if (!token) {
token = await this.registerForToken();
this.accessToken = token;
window.localStorage.setItem("mx_is_access_token", token);
}

try {
await this._checkToken(token);
} catch (e) {
// Retry in case token expired
token = await this.registerForToken();
this.accessToken = token;
window.localStorage.setItem("mx_is_access_token", token);
}

return token;
}

_checkToken(token) {
// TODO: Test current API token via `/account` endpoint

// At the moment, Sydent doesn't implement `/account`, so we can't use
// that yet. We could try a lookup for a null address perhaps...?
// Sydent doesn't currently expire tokens, but we should still be testing
// them in any case.
// See also https://github.com/vector-im/riot-web/issues/10452.

// In any case, we should ensure the token in `localStorage` is cleared
// appropriately. We already clear storage on sign out, but we'll need
// additional clearing when changing ISes in settings as part of future
// privacy work.
// See also https://github.com/vector-im/riot-web/issues/10455.
}

async registerForToken() {
try {
const hsOpenIdToken = await MatrixClientPeg.get().getOpenIdToken();
const { access_token: identityAccessToken } =
await MatrixClientPeg.get().registerWithIdentityServer(hsOpenIdToken);
await this._checkToken(identityAccessToken);
return identityAccessToken;
} catch (err) {
if (err.cors === "rejected" || err.httpStatus === 404) {
// Assume IS only supports deprecated v1 API for now
// TODO: Remove this path once v2 is only supported version
// See https://github.com/vector-im/riot-web/issues/10443
console.warn("IS doesn't support v2 auth");
this.authEnabled = false;
}
}
}
}
57 changes: 36 additions & 21 deletions src/components/views/dialogs/AddressPickerDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017, 2018, 2019 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -18,13 +19,15 @@ limitations under the License.

import React from 'react';
import PropTypes from 'prop-types';

import { _t, _td } from '../../../languageHandler';
import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
import Promise from 'bluebird';
import { addressTypes, getAddressType } from '../../../UserAddress.js';
import GroupStore from '../../../stores/GroupStore';
import * as Email from "../../../email";
import * as Email from '../../../email';
import IdentityAuthClient from '../../../IdentityAuthClient';

const TRUNCATE_QUERY_LIST = 40;
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
Expand Down Expand Up @@ -71,12 +74,11 @@ module.exports = React.createClass({

getInitialState: function() {
return {
error: false,

// Whether to show an error message because of an invalid address
invalidAddressError: false,
// List of UserAddressType objects representing
// the list of addresses we're going to invite
selectedList: [],

// Whether a search is ongoing
busy: false,
// An error message generated during the user directory search
Expand Down Expand Up @@ -443,12 +445,12 @@ module.exports = React.createClass({
});
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
if (addrType === 'email') {
this._lookupThreepid(addrType, query).done();
this._lookupThreepid(addrType, query);
}
}
this.setState({
suggestedList,
error: false,
invalidAddressError: false,
}, () => {
if (this.addressSelector) this.addressSelector.moveSelectionTop();
});
Expand Down Expand Up @@ -492,13 +494,13 @@ module.exports = React.createClass({
selectedList,
suggestedList: [],
query: "",
error: hasError ? true : this.state.error,
invalidAddressError: hasError ? true : this.state.invalidAddressError,
});
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
return hasError ? null : selectedList;
},

_lookupThreepid: function(medium, address) {
_lookupThreepid: async function(medium, address) {
let cancelled = false;
// Note that we can't safely remove this after we're done
// because we don't know that it's the same one, so we just
Expand All @@ -509,28 +511,41 @@ module.exports = React.createClass({
};

// wait a bit to let the user finish typing
return Promise.delay(500).then(() => {
if (cancelled) return null;
return MatrixClientPeg.get().lookupThreePid(medium, address);
}).then((res) => {
if (res === null || !res.mxid) return null;
if (cancelled) return null;
await Promise.delay(500);
if (cancelled) return null;

return MatrixClientPeg.get().getProfileInfo(res.mxid);
}).then((res) => {
if (res === null) return null;
try {
const authClient = new IdentityAuthClient();
const identityAccessToken = await authClient.getAccessToken();
if (cancelled) return null;

const lookup = await MatrixClientPeg.get().lookupThreePid(
medium,
address,
undefined /* callback */,
identityAccessToken,
);
if (cancelled || lookup === null || !lookup.mxid) return null;

const profile = await MatrixClientPeg.get().getProfileInfo(lookup.mxid);
if (cancelled || profile === null) return null;

this.setState({
suggestedList: [{
// a UserAddressType
addressType: medium,
address: address,
displayName: res.displayname,
avatarMxc: res.avatar_url,
displayName: profile.displayname,
avatarMxc: profile.avatar_url,
isKnown: true,
}],
});
});
} catch (e) {
console.error(e);
this.setState({
searchError: _t('Something went wrong!'),
});
}
},

_getFilteredSuggestions: function() {
Expand Down Expand Up @@ -597,7 +612,7 @@ module.exports = React.createClass({

let error;
let addressSelector;
if (this.state.error) {
if (this.state.invalidAddressError) {
const validTypeDescriptions = this.props.validAddressTypes.map((t) => _t(addressTypeName[t]));
error = <div className="mx_AddressPickerDialog_error">
{ _t("You have entered an invalid address.") }
Expand Down
22 changes: 14 additions & 8 deletions src/components/views/rooms/RoomPreviewBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
import dis from '../../../dispatcher';
import classNames from 'classnames';
import { _t } from '../../../languageHandler';
import IdentityAuthClient from '../../../IdentityAuthClient';

const MessageCase = Object.freeze({
NotLoggedIn: "NotLoggedIn",
Expand Down Expand Up @@ -104,21 +105,26 @@ module.exports = React.createClass({
}
},

_checkInvitedEmail: function() {
_checkInvitedEmail: async function() {
// If this is an invite and we've been told what email
// address was invited, fetch the user's list of Threepids
// so we can check them against the one that was invited
if (this.props.inviterName && this.props.invitedEmail) {
this.setState({busy: true});
MatrixClientPeg.get().lookupThreePid(
'email', this.props.invitedEmail,
).finally(() => {
this.setState({busy: false});
}).done((result) => {
try {
const authClient = new IdentityAuthClient();
const identityAccessToken = await authClient.getAccessToken();
const result = await MatrixClientPeg.get().lookupThreePid(
'email',
this.props.invitedEmail,
undefined /* callback */,
identityAccessToken,
);
this.setState({invitedEmailMxid: result.mxid});
}, (err) => {
} catch (err) {
this.setState({threePidFetchError: err});
});
}
this.setState({busy: false});
}
},

Expand Down

0 comments on commit a9a33f5

Please sign in to comment.