First iteration of OpenID reauth widget example

This commit is contained in:
Travis Ralston 2019-03-13 00:28:12 -06:00
parent 34653eb223
commit 11ed6342f6
9 changed files with 191 additions and 3 deletions

View file

@ -109,6 +109,7 @@ import { SlackBridgeConfigComponent } from "./configs/bridge/slack/slack.bridge.
import { AdminSlackBridgeManageSelfhostedComponent } from "./admin/bridges/slack/manage-selfhosted/manage-selfhosted.component";
import { AdminSlackBridgeComponent } from "./admin/bridges/slack/slack.component";
import { AdminSlackApiService } from "./shared/services/admin/admin-slack-api.service";
import { ReauthExampleWidgetWrapperComponent } from "./widget-wrappers/reauth-example/reauth-example.component";
@NgModule({
imports: [
@ -198,6 +199,7 @@ import { AdminSlackApiService } from "./shared/services/admin/admin-slack-api.se
SlackBridgeConfigComponent,
AdminSlackBridgeManageSelfhostedComponent,
AdminSlackBridgeComponent,
ReauthExampleWidgetWrapperComponent,
// Vendor
],

View file

@ -42,6 +42,7 @@ import { SpotifyWidgetWrapperComponent } from "./widget-wrappers/spotify/spotify
import { AdminCustomBotsComponent } from "./admin/custom-bots/custom-bots.component";
import { AdminSlackBridgeComponent } from "./admin/bridges/slack/slack.component";
import { SlackBridgeConfigComponent } from "./configs/bridge/slack/slack.bridge.component";
import { ReauthExampleWidgetWrapperComponent } from "./widget-wrappers/reauth-example/reauth-example.component";
const routes: Routes = [
{path: "", component: HomeComponent},
@ -264,6 +265,7 @@ const routes: Routes = [
{path: "generic-fullscreen", component: GenericFullscreenWidgetWrapperComponent},
{path: "tradingview", component: TradingViewWidgetWrapperComponent},
{path: "spotify", component: SpotifyWidgetWrapperComponent},
{path: "reauth", component: ReauthExampleWidgetWrapperComponent},
]
},
];

View file

@ -1,3 +1,14 @@
export interface FE_ScalarAccountResponse {
user_id: string;
}
export interface FE_ScalarRegisterResponse {
scalar_token: string;
}
export interface FE_ScalarOpenIdRequestBody {
access_token: string;
expires_in: number;
matrix_server_name: string;
token_type: string;
}

View file

@ -1,7 +1,16 @@
export interface ScalarToWidgetRequest {
api: "to_widget";
action: "supported_api_versions" | "screenshot" | "capabilities" | "send_event" | "visibility" | string;
action: "supported_api_versions" | "screenshot" | "capabilities" | "send_event" | "visibility" | "openid_credentials" | string;
requestId: string;
widgetId: string;
data?: any;
}
export interface ScalarFromWidgetResponse {
api: "from_widget";
action: "get_openid" | string;
requestId: string;
widgetId: string;
data?: any;
response: any;
}

View file

@ -1,6 +1,10 @@
import { Injectable } from "@angular/core";
import { Http } from "@angular/http";
import { FE_ScalarAccountResponse } from "../../models/scalar-server-responses";
import {
FE_ScalarAccountResponse,
FE_ScalarOpenIdRequestBody,
FE_ScalarRegisterResponse
} from "../../models/scalar-server-responses";
import { AuthedApi } from "../authed-api";
@Injectable()
@ -12,4 +16,8 @@ export class ScalarServerApiService extends AuthedApi {
public getAccount(): Promise<FE_ScalarAccountResponse> {
return this.authedGet("/api/v1/scalar/account").map(res => res.json()).toPromise();
}
public register(openId: FE_ScalarOpenIdRequestBody): Promise<FE_ScalarRegisterResponse> {
return this.http.post("/api/v1/scalar/register", openId).map(res => res.json()).toPromise();
}
}

View file

@ -1,4 +1,4 @@
import { ScalarToWidgetRequest } from "../../models/scalar-widget-actions";
import { ScalarFromWidgetResponse, ScalarToWidgetRequest } from "../../models/scalar-widget-actions";
import { ReplaySubject } from "rxjs/ReplaySubject";
import { Subject } from "rxjs/Subject";
import { FE_Sticker, FE_StickerPack } from "../../models/integration";
@ -7,7 +7,9 @@ import * as randomString from "random-string";
export class ScalarWidgetApi {
public static requestReceived: Subject<ScalarToWidgetRequest> = new ReplaySubject();
public static replyReceived: Subject<ScalarFromWidgetResponse> = new ReplaySubject();
public static widgetId: string;
public static inFlightRequestIds: string[] = [];
private constructor() {
}
@ -82,6 +84,10 @@ export class ScalarWidgetApi {
});
}
public static requestOpenID(): void {
ScalarWidgetApi.callAction("get_openid", {});
}
private static callAction(action, payload) {
if (!window.opener) {
return;
@ -93,6 +99,8 @@ export class ScalarWidgetApi {
request["action"] = action;
request["requestId"] = randomString({length: 160});
ScalarWidgetApi.inFlightRequestIds.push(request["requestId"]);
console.log("[Dimension] Sending fromWidget: ", request);
window.opener.postMessage(request, "*");
}
@ -120,4 +128,15 @@ window.addEventListener("message", event => {
ScalarWidgetApi.requestReceived.next(event.data);
return;
}
if (event.data.api === "fromWidget" && ScalarWidgetApi.inFlightRequestIds.indexOf(event.data.requestId) !== -1) {
if (event.data.widgetId && !ScalarWidgetApi.widgetId) ScalarWidgetApi.widgetId = event.data.widgetId;
console.log(`[Dimension] Received fromWidget response at widget ID ${ScalarWidgetApi.widgetId}: ${JSON.stringify(event.data)}`);
const idx = ScalarWidgetApi.inFlightRequestIds.indexOf(event.data.requestId);
ScalarWidgetApi.inFlightRequestIds.splice(idx, 1);
ScalarWidgetApi.replyReceived.next(event.data);
return;
}
});

View file

@ -0,0 +1,14 @@
<div *ngIf="!hasOpenId" class="reauth-container">
<button type="button" (click)="onReauthStart()" class="btn btn-primary" [disabled]="busy">
Click to start OpenID auth
</button>
</div>
<div *ngIf="hasOpenId" class="reauth-container">
<p>User ID: {{userId}}</p>
</div>
<div class="reauth-container">
<p>{{stateMessage}}</p>
<p *ngIf="blocked">You have blocked this widget from receiving credentials.</p>
<p *ngIf="error">An error has occurred - see logs for details</p>
<small>Widget ID: {{widgetId}}</small>
</div>

View file

@ -0,0 +1,4 @@
.reauth-container {
padding-top: 20px;
text-align: center;
}

View file

@ -0,0 +1,119 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core";
import { ScalarWidgetApi } from "../../shared/services/scalar/scalar-widget.api";
import { Subscription } from "rxjs";
import { CapableWidget } from "../capable-widget";
import { ActivatedRoute } from "@angular/router";
import { ScalarServerApiService } from "../../shared/services/scalar/scalar-server-api.service";
import { SessionStorage } from "../../shared/SessionStorage";
import { FE_ScalarOpenIdRequestBody } from "../../shared/models/scalar-server-responses";
@Component({
selector: "my-reauth-example-widget-wrapper",
templateUrl: "reauth-example.component.html",
styleUrls: ["reauth-example.component.scss"],
})
export class ReauthExampleWidgetWrapperComponent extends CapableWidget implements OnInit, OnDestroy {
public busy = false;
public hasOpenId = false;
public userId: string;
public blocked = false;
public error = false;
public stateMessage = "";
private widgetReplySubscription: Subscription;
private widgetRequestSubscription: Subscription;
constructor(activatedRoute: ActivatedRoute,
private scalarApi: ScalarServerApiService,
private changeDetector: ChangeDetectorRef) {
super();
const params: any = activatedRoute.snapshot.queryParams;
ScalarWidgetApi.widgetId = params.widgetId;
}
public get widgetId(): string {
return ScalarWidgetApi.widgetId;
}
public ngOnInit(): void {
// TODO: Check that the client supports this (API version 0.0.2 at least)
super.ngOnInit();
this.widgetReplySubscription = ScalarWidgetApi.replyReceived.subscribe(async response => {
if (response.action !== "get_openid") return;
const data = response.response;
try {
if (data.state === "request") {
this.stateMessage = "Waiting for you to accept the prompt...";
} else if (data.state === "allowed") {
await this.exchangeOpenIdInfo(data);
} else {
this.blocked = true;
this.busy = false;
this.hasOpenId = false;
this.stateMessage = null;
}
} catch (e) {
console.error(e);
this.error = true;
this.busy = false;
this.stateMessage = null;
}
this.changeDetector.detectChanges();
});
this.widgetRequestSubscription = ScalarWidgetApi.requestReceived.subscribe(async request => {
if (request.action !== "openid_credentials") return;
ScalarWidgetApi.replyAcknowledge(request);
try {
if (request.data.success) {
await this.exchangeOpenIdInfo(request.data);
} else {
this.blocked = true;
this.busy = false;
this.stateMessage = null;
}
} catch (e) {
console.error(e);
this.error = true;
this.busy = false;
this.stateMessage = null;
}
this.changeDetector.detectChanges();
});
}
public ngOnDestroy() {
super.ngOnDestroy();
if (this.widgetReplySubscription) this.widgetReplySubscription.unsubscribe();
if (this.widgetRequestSubscription) this.widgetRequestSubscription.unsubscribe();
}
public onReauthStart(): void {
this.busy = true;
this.error = false;
this.blocked = false;
this.hasOpenId = false;
ScalarWidgetApi.requestOpenID();
}
private async exchangeOpenIdInfo(openId: FE_ScalarOpenIdRequestBody) {
this.stateMessage = "Exchanging OpenID credentials for token...";
this.changeDetector.detectChanges();
const scalarTokenResp = await this.scalarApi.register(openId);
SessionStorage.scalarToken = scalarTokenResp.scalar_token;
const userInfo = await this.scalarApi.getAccount();
this.hasOpenId = true;
this.userId = userInfo.user_id;
this.blocked = false;
this.busy = false;
this.stateMessage = null;
}
}