Basic widget OpenID reauth implementation
Covers the minimum of https://github.com/vector-im/riot-web/issues/7153 This does not handling automatically accepting/blocking widgets yet, however. This could lead to dialog irritation.
This commit is contained in:
parent
c0bb9c8c9c
commit
d63c5e7134
3 changed files with 88 additions and 3 deletions
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2018 New Vector Ltd
|
Copyright 2018 New Vector Ltd
|
||||||
|
Copyright 2019 Travis Ralston
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the 'License');
|
Licensed under the Apache License, Version 2.0 (the 'License');
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -20,17 +21,19 @@ import IntegrationManager from './IntegrationManager';
|
||||||
import WidgetMessagingEndpoint from './WidgetMessagingEndpoint';
|
import WidgetMessagingEndpoint from './WidgetMessagingEndpoint';
|
||||||
import ActiveWidgetStore from './stores/ActiveWidgetStore';
|
import ActiveWidgetStore from './stores/ActiveWidgetStore';
|
||||||
|
|
||||||
const WIDGET_API_VERSION = '0.0.1'; // Current API version
|
const WIDGET_API_VERSION = '0.0.2'; // Current API version
|
||||||
const SUPPORTED_WIDGET_API_VERSIONS = [
|
const SUPPORTED_WIDGET_API_VERSIONS = [
|
||||||
'0.0.1',
|
'0.0.1',
|
||||||
|
'0.0.2',
|
||||||
];
|
];
|
||||||
const INBOUND_API_NAME = 'fromWidget';
|
const INBOUND_API_NAME = 'fromWidget';
|
||||||
|
|
||||||
// Listen for and handle incomming requests using the 'fromWidget' postMessage
|
// Listen for and handle incoming requests using the 'fromWidget' postMessage
|
||||||
// API and initiate responses
|
// API and initiate responses
|
||||||
export default class FromWidgetPostMessageApi {
|
export default class FromWidgetPostMessageApi {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.widgetMessagingEndpoints = [];
|
this.widgetMessagingEndpoints = [];
|
||||||
|
this.widgetListeners = {}; // {action: func[]}
|
||||||
|
|
||||||
this.start = this.start.bind(this);
|
this.start = this.start.bind(this);
|
||||||
this.stop = this.stop.bind(this);
|
this.stop = this.stop.bind(this);
|
||||||
|
@ -45,6 +48,32 @@ export default class FromWidgetPostMessageApi {
|
||||||
window.removeEventListener('message', this.onPostMessage);
|
window.removeEventListener('message', this.onPostMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a listener for a given action
|
||||||
|
* @param {string} action The action to listen for.
|
||||||
|
* @param {Function} callbackFn A callback function to be called when the action is
|
||||||
|
* encountered. Called with two parameters: the interesting request information and
|
||||||
|
* the raw event received from the postMessage API. The raw event is meant to be used
|
||||||
|
* for sendResponse and similar functions.
|
||||||
|
*/
|
||||||
|
addListener(action, callbackFn) {
|
||||||
|
if (!this.widgetListeners[action]) this.widgetListeners[action] = [];
|
||||||
|
this.widgetListeners[action].push(callbackFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a listener for a given action.
|
||||||
|
* @param {string} action The action that was subscribed to.
|
||||||
|
* @param {Function} callbackFn The original callback function that was used to subscribe
|
||||||
|
* to updates.
|
||||||
|
*/
|
||||||
|
removeListener(action, callbackFn) {
|
||||||
|
if (!this.widgetListeners[action]) return;
|
||||||
|
|
||||||
|
const idx = this.widgetListeners.indexOf(callbackFn);
|
||||||
|
if (idx !== -1) this.widgetListeners.splice(idx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a widget endpoint for trusted postMessage communication
|
* Register a widget endpoint for trusted postMessage communication
|
||||||
* @param {string} widgetId Unique widget identifier
|
* @param {string} widgetId Unique widget identifier
|
||||||
|
@ -117,6 +146,13 @@ export default class FromWidgetPostMessageApi {
|
||||||
return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise
|
return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call any listeners we have registered
|
||||||
|
if (this.widgetListeners[event.data.action]) {
|
||||||
|
for (const fn of this.widgetListeners[event.data.action]) {
|
||||||
|
fn(event.data, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Although the requestId is required, we don't use it. We'll be nice and process the message
|
// Although the requestId is required, we don't use it. We'll be nice and process the message
|
||||||
// if the property is missing, but with a warning for widget developers.
|
// if the property is missing, but with a warning for widget developers.
|
||||||
if (!event.data.requestId) {
|
if (!event.data.requestId) {
|
||||||
|
@ -164,6 +200,8 @@ export default class FromWidgetPostMessageApi {
|
||||||
if (ActiveWidgetStore.widgetHasCapability(widgetId, 'm.always_on_screen')) {
|
if (ActiveWidgetStore.widgetHasCapability(widgetId, 'm.always_on_screen')) {
|
||||||
ActiveWidgetStore.setWidgetPersistence(widgetId, val);
|
ActiveWidgetStore.setWidgetPersistence(widgetId, val);
|
||||||
}
|
}
|
||||||
|
} else if (action === 'get_openid') {
|
||||||
|
// Handled by caller
|
||||||
} else {
|
} else {
|
||||||
console.warn('Widget postMessage event unhandled');
|
console.warn('Widget postMessage event unhandled');
|
||||||
this.sendError(event, {message: 'The postMessage was unhandled'});
|
this.sendError(event, {message: 'The postMessage was unhandled'});
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2017 New Vector Ltd
|
Copyright 2017 New Vector Ltd
|
||||||
|
Copyright 2019 Travis Ralston
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -21,6 +22,10 @@ limitations under the License.
|
||||||
|
|
||||||
import FromWidgetPostMessageApi from './FromWidgetPostMessageApi';
|
import FromWidgetPostMessageApi from './FromWidgetPostMessageApi';
|
||||||
import ToWidgetPostMessageApi from './ToWidgetPostMessageApi';
|
import ToWidgetPostMessageApi from './ToWidgetPostMessageApi';
|
||||||
|
import Modal from "./Modal";
|
||||||
|
import QuestionDialog from "./components/views/dialogs/QuestionDialog";
|
||||||
|
import {_t} from "./languageHandler";
|
||||||
|
import MatrixClientPeg from "./MatrixClientPeg";
|
||||||
|
|
||||||
if (!global.mxFromWidgetMessaging) {
|
if (!global.mxFromWidgetMessaging) {
|
||||||
global.mxFromWidgetMessaging = new FromWidgetPostMessageApi();
|
global.mxFromWidgetMessaging = new FromWidgetPostMessageApi();
|
||||||
|
@ -40,6 +45,7 @@ export default class WidgetMessaging {
|
||||||
this.target = target;
|
this.target = target;
|
||||||
this.fromWidget = global.mxFromWidgetMessaging;
|
this.fromWidget = global.mxFromWidgetMessaging;
|
||||||
this.toWidget = global.mxToWidgetMessaging;
|
this.toWidget = global.mxToWidgetMessaging;
|
||||||
|
this._openIdHandlerRef = this._onOpenIdRequest.bind(this);
|
||||||
this.start();
|
this.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,9 +115,48 @@ export default class WidgetMessaging {
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
this.fromWidget.addEndpoint(this.widgetId, this.widgetUrl);
|
this.fromWidget.addEndpoint(this.widgetId, this.widgetUrl);
|
||||||
|
this.fromWidget.addListener("get_openid", this._openIdHandlerRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
this.fromWidget.removeEndpoint(this.widgetId, this.widgetUrl);
|
this.fromWidget.removeEndpoint(this.widgetId, this.widgetUrl);
|
||||||
|
this.fromWidget.removeListener("get_openid", this._openIdHandlerRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onOpenIdRequest(ev, rawEv) {
|
||||||
|
if (ev.widgetId !== this.widgetId) return; // not interesting
|
||||||
|
|
||||||
|
// Confirm that we received the request
|
||||||
|
this.fromWidget.sendResponse(rawEv, {state: "request"});
|
||||||
|
|
||||||
|
// TODO: Support blacklisting widgets
|
||||||
|
// TODO: Support whitelisting widgets
|
||||||
|
|
||||||
|
// Actually ask for permission to send the user's data
|
||||||
|
Modal.createTrackedDialog("OpenID widget permissions", '', QuestionDialog, {
|
||||||
|
title: _t("A widget would like to verify your identity"),
|
||||||
|
description: _t(
|
||||||
|
"A widget located at %(widgetUrl)s would like to verify your identity. " +
|
||||||
|
"By allowing this, the widget will be able to verify your user ID, but not " +
|
||||||
|
"perform actions as you.", {
|
||||||
|
widgetUrl: this.widgetUrl,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
button: _t("Allow"),
|
||||||
|
onFinished: async (confirm) => {
|
||||||
|
const responseBody = {success: confirm};
|
||||||
|
if (confirm) {
|
||||||
|
const credentials = await MatrixClientPeg.get().getOpenIdToken();
|
||||||
|
Object.assign(responseBody, credentials);
|
||||||
|
}
|
||||||
|
this.messageToWidget({
|
||||||
|
api: OUTBOUND_API_NAME,
|
||||||
|
action: "openid_credentials",
|
||||||
|
data: responseBody,
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error("Failed to send OpenID credentials: ", error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,6 +230,9 @@
|
||||||
"%(names)s and %(count)s others are typing …|other": "%(names)s and %(count)s others are typing …",
|
"%(names)s and %(count)s others are typing …|other": "%(names)s and %(count)s others are typing …",
|
||||||
"%(names)s and %(count)s others are typing …|one": "%(names)s and one other is typing …",
|
"%(names)s and %(count)s others are typing …|one": "%(names)s and one other is typing …",
|
||||||
"%(names)s and %(lastPerson)s are typing …": "%(names)s and %(lastPerson)s are typing …",
|
"%(names)s and %(lastPerson)s are typing …": "%(names)s and %(lastPerson)s are typing …",
|
||||||
|
"A widget would like to verify your identity": "A widget would like to verify your identity",
|
||||||
|
"A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.": "A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.",
|
||||||
|
"Allow": "Allow",
|
||||||
"This homeserver has hit its Monthly Active User limit.": "This homeserver has hit its Monthly Active User limit.",
|
"This homeserver has hit its Monthly Active User limit.": "This homeserver has hit its Monthly Active User limit.",
|
||||||
"This homeserver has exceeded one of its resource limits.": "This homeserver has exceeded one of its resource limits.",
|
"This homeserver has exceeded one of its resource limits.": "This homeserver has exceeded one of its resource limits.",
|
||||||
"Please <a>contact your service administrator</a> to continue using the service.": "Please <a>contact your service administrator</a> to continue using the service.",
|
"Please <a>contact your service administrator</a> to continue using the service.": "Please <a>contact your service administrator</a> to continue using the service.",
|
||||||
|
@ -924,7 +927,6 @@
|
||||||
"NOTE: Apps are not end-to-end encrypted": "NOTE: Apps are not end-to-end encrypted",
|
"NOTE: Apps are not end-to-end encrypted": "NOTE: Apps are not end-to-end encrypted",
|
||||||
"Warning: This widget might use cookies.": "Warning: This widget might use cookies.",
|
"Warning: This widget might use cookies.": "Warning: This widget might use cookies.",
|
||||||
"Do you want to load widget from URL:": "Do you want to load widget from URL:",
|
"Do you want to load widget from URL:": "Do you want to load widget from URL:",
|
||||||
"Allow": "Allow",
|
|
||||||
"Delete Widget": "Delete Widget",
|
"Delete Widget": "Delete Widget",
|
||||||
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?",
|
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?",
|
||||||
"Delete widget": "Delete widget",
|
"Delete widget": "Delete widget",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue