sources/plex: add API to redeem token
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
@ -23,6 +23,7 @@ import "./stages/email/EmailStage";
|
||||
import "./stages/identification/IdentificationStage";
|
||||
import "./stages/password/PasswordStage";
|
||||
import "./stages/prompt/PromptStage";
|
||||
import "./sources/plex/PlexLoginInit";
|
||||
import { ShellChallenge, RedirectChallenge } from "../api/Flows";
|
||||
import { IdentificationChallenge } from "./stages/identification/IdentificationStage";
|
||||
import { PasswordChallenge } from "./stages/password/PasswordStage";
|
||||
@ -44,6 +45,7 @@ import { AccessDeniedChallenge } from "./access_denied/FlowAccessDenied";
|
||||
import { PFSize } from "../elements/Spinner";
|
||||
import { TITLE_DEFAULT } from "../constants";
|
||||
import { configureSentry } from "../api/Sentry";
|
||||
import { PlexAuthenticationChallenge } from "./sources/plex/PlexLoginInit";
|
||||
|
||||
@customElement("ak-flow-executor")
|
||||
export class FlowExecutor extends LitElement implements StageHost {
|
||||
@ -223,6 +225,8 @@ export class FlowExecutor extends LitElement implements StageHost {
|
||||
return html`<ak-stage-authenticator-webauthn .host=${this} .challenge=${this.challenge as WebAuthnAuthenticatorRegisterChallenge}></ak-stage-authenticator-webauthn>`;
|
||||
case "ak-stage-authenticator-validate":
|
||||
return html`<ak-stage-authenticator-validate .host=${this} .challenge=${this.challenge as AuthenticatorValidateStageChallenge}></ak-stage-authenticator-validate>`;
|
||||
case "ak-flow-sources-plex":
|
||||
return html`<ak-flow-sources-plex .host=${this} .challenge=${this.challenge as PlexAuthenticationChallenge}></ak-flow-sources-plex>`;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -21,6 +21,12 @@ export const DEFAULT_HEADERS = {
|
||||
"X-Plex-Device-Vendor": "BeryJu.org",
|
||||
};
|
||||
|
||||
export function popupCenterScreen(url: string, title: string, w: number, h: number): Window | null {
|
||||
const top = (screen.height - h) / 4, left = (screen.width - w) / 2;
|
||||
const popup = window.open(url, title, `scrollbars=yes,width=${w},height=${h},top=${top},left=${left}`);
|
||||
return popup;
|
||||
}
|
||||
|
||||
export class PlexAPIClient {
|
||||
|
||||
token: string;
|
||||
@ -44,14 +50,38 @@ export class PlexAPIClient {
|
||||
};
|
||||
}
|
||||
|
||||
static async pinStatus(id: number): Promise<string> {
|
||||
static async pinStatus(clientIdentifier: string, id: number): Promise<string | undefined> {
|
||||
const headers = { ...DEFAULT_HEADERS, ...{
|
||||
"X-Plex-Client-Identifier": clientIdentifier
|
||||
}};
|
||||
const pinResponse = await fetch(`https://plex.tv/api/v2/pins/${id}`, {
|
||||
headers: DEFAULT_HEADERS
|
||||
headers: headers
|
||||
});
|
||||
const pin: PlexPinResponse = await pinResponse.json();
|
||||
return pin.authToken || "";
|
||||
}
|
||||
|
||||
static async pinPoll(clientIdentifier: string, id: number): Promise<string> {
|
||||
const executePoll = async (
|
||||
resolve: (authToken: string) => void,
|
||||
reject: (e: Error) => void
|
||||
) => {
|
||||
try {
|
||||
const response = await PlexAPIClient.pinStatus(clientIdentifier, id)
|
||||
|
||||
if (response) {
|
||||
resolve(response);
|
||||
} else {
|
||||
setTimeout(executePoll, 500, resolve, reject);
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise(executePoll);
|
||||
}
|
||||
|
||||
async getServers(): Promise<PlexResource[]> {
|
||||
const resourcesResponse = await fetch(`https://plex.tv/api/v2/resources?X-Plex-Token=${this.token}&X-Plex-Client-Identifier=authentik`, {
|
||||
headers: DEFAULT_HEADERS
|
||||
|
@ -1,11 +1,45 @@
|
||||
import {customElement, LitElement} from "lit-element";
|
||||
import { Challenge } from "authentik-api";
|
||||
import {customElement, property} from "lit-element";
|
||||
import {html, TemplateResult} from "lit-html";
|
||||
import { PFSize } from "../../../elements/Spinner";
|
||||
import { BaseStage } from "../../stages/base";
|
||||
import {PlexAPIClient, popupCenterScreen} from "./API";
|
||||
import {DEFAULT_CONFIG} from "../../../api/Config";
|
||||
import { SourcesApi } from "authentik-api";
|
||||
|
||||
export interface PlexAuthenticationChallenge extends Challenge {
|
||||
|
||||
client_id: string;
|
||||
slug: string;
|
||||
|
||||
}
|
||||
|
||||
@customElement("ak-flow-sources-plex")
|
||||
export class PlexLoginInit extends LitElement {
|
||||
export class PlexLoginInit extends BaseStage {
|
||||
|
||||
render(): TemplateResult {
|
||||
return html``;
|
||||
@property({ attribute: false })
|
||||
challenge?: PlexAuthenticationChallenge;
|
||||
|
||||
async firstUpdated(): Promise<void> {
|
||||
const authInfo = await PlexAPIClient.getPin(this.challenge?.client_id || "");
|
||||
const authWindow = popupCenterScreen(authInfo.authUrl, "plex auth", 550, 700);
|
||||
PlexAPIClient.pinPoll(this.challenge?.client_id || "", authInfo.pin.id).then(token => {
|
||||
authWindow?.close();
|
||||
new SourcesApi(DEFAULT_CONFIG).sourcesPlexRedeemToken({
|
||||
data: {
|
||||
plexToken: token,
|
||||
},
|
||||
slug: this.challenge?.slug || "",
|
||||
}).then(r => {
|
||||
window.location.assign(r.to);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
renderLoading(): TemplateResult {
|
||||
return html`<div class="ak-loading">
|
||||
<ak-spinner size=${PFSize.XLarge}></ak-spinner>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,15 +9,9 @@ import "../../../elements/forms/HorizontalFormElement";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import { first, randomString } from "../../../utils";
|
||||
import { PlexAPIClient, PlexResource} from "../../../flows/sources/plex/API";
|
||||
import { PlexAPIClient, PlexResource, popupCenterScreen} from "../../../flows/sources/plex/API";
|
||||
|
||||
|
||||
function popupCenterScreen(url: string, title: string, w: number, h: number): Window | null {
|
||||
const top = (screen.height - h) / 4, left = (screen.width - w) / 2;
|
||||
const popup = window.open(url, title, `scrollbars=yes,width=${w},height=${h},top=${top},left=${left}`);
|
||||
return popup;
|
||||
}
|
||||
|
||||
@customElement("ak-source-plex-form")
|
||||
export class PlexSourceForm extends Form<PlexSource> {
|
||||
|
||||
@ -64,15 +58,11 @@ export class PlexSourceForm extends Form<PlexSource> {
|
||||
async doAuth(): Promise<void> {
|
||||
const authInfo = await PlexAPIClient.getPin(this.source?.clientId);
|
||||
const authWindow = popupCenterScreen(authInfo.authUrl, "plex auth", 550, 700);
|
||||
const timer = setInterval(() => {
|
||||
if (authWindow?.closed) {
|
||||
clearInterval(timer);
|
||||
PlexAPIClient.pinStatus(authInfo.pin.id).then((token: string) => {
|
||||
this.plexToken = token;
|
||||
this.loadServers();
|
||||
});
|
||||
}
|
||||
}, 500);
|
||||
PlexAPIClient.pinPoll(this.source?.clientId || "", authInfo.pin.id).then(token => {
|
||||
authWindow?.close();
|
||||
this.plexToken = token;
|
||||
this.loadServers();
|
||||
});
|
||||
}
|
||||
|
||||
async loadServers(): Promise<void> {
|
||||
|
Reference in New Issue
Block a user