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
	 Jens Langhammer
					Jens Langhammer