11import { maybeRedirectToWelcomePage } from '../app/actions.web' ;
22import { IStore } from '../app/types' ;
33import { openDialog } from '../base/dialog/actions' ;
4+ import { setJWT } from '../base/jwt/actions' ;
45import { browser } from '../base/lib-jitsi-meet' ;
6+ import { showErrorNotification } from '../notifications/actions' ;
57
68import { CANCEL_LOGIN } from './actionTypes' ;
79import LoginQuestionDialog from './components/web/LoginQuestionDialog' ;
10+ import { isTokenAuthInline } from './functions.any' ;
11+ import logger from './logger' ;
812
913export * from './actions.any' ;
1014
@@ -46,6 +50,134 @@ export function redirectToDefaultLocation() {
4650 return ( dispatch : IStore [ 'dispatch' ] ) => dispatch ( maybeRedirectToWelcomePage ( ) ) ;
4751}
4852
53+ /**
54+ * Generates a cryptographic nonce.
55+ *
56+ * @returns {string } The generated nonce.
57+ */
58+ function generateNonce ( ) : string {
59+ const array = new Uint8Array ( 32 ) ;
60+
61+ crypto . getRandomValues ( array ) ;
62+
63+ return Array . from ( array , byte => byte . toString ( 16 ) . padStart ( 2 , '0' ) ) . join ( '' ) ;
64+ }
65+
66+ /**
67+ * Performs login with a popup window.
68+ *
69+ * @param {string } tokenAuthServiceUrl - Authentication service URL.
70+ * @returns {Promise<any> } A promise that resolves with the authentication
71+ * result or rejects with an error.
72+ */
73+ export function loginWithPopup ( tokenAuthServiceUrl : string ) : Promise < any > {
74+ return new Promise < any > ( ( resolve , reject ) => {
75+ // Open popup
76+ const width = 500 ;
77+ const height = 600 ;
78+ const left = window . screen . width / 2 - width / 2 ;
79+ const top = window . screen . height / 2 - height / 2 ;
80+
81+ const nonce = generateNonce ( ) ;
82+
83+ sessionStorage . setItem ( 'oauth_nonce' , nonce ) ;
84+
85+ const popup = window . open (
86+ `${ tokenAuthServiceUrl } &nonce=${ nonce } ` ,
87+ `Auth-${ Date . now ( ) } ` ,
88+ `width=${ width } ,height=${ height } ,left=${ left } ,top=${ top } `
89+ ) ;
90+
91+ if ( ! popup ) {
92+ reject ( new Error ( 'Popup blocked' ) ) ;
93+
94+ return ;
95+ }
96+
97+ // @ts -ignore
98+ const handler = event => {
99+ // Verify origin
100+ if ( event . origin !== window . location . origin ) {
101+ return ;
102+ }
103+
104+ if ( event . data . type === 'oauth-success' ) {
105+ window . removeEventListener ( 'message' , handler ) ;
106+ popup . close ( ) ;
107+
108+ sessionStorage . removeItem ( 'oauth_nonce' ) ;
109+
110+ resolve ( {
111+ accessToken : event . data . accessToken ,
112+ idToken : event . data . idToken ,
113+ refreshToken : event . data . refreshToken
114+ } ) ;
115+ } else if ( event . data . type === 'oauth-error' ) {
116+ window . removeEventListener ( 'message' , handler ) ;
117+ popup . close ( ) ;
118+ reject ( new Error ( event . data . error ) ) ;
119+ }
120+ } ;
121+
122+ // Listen for messages from the popup
123+ window . addEventListener ( 'message' , handler ) ;
124+
125+ // Check if popup was closed
126+ const checkClosed = setInterval ( ( ) => {
127+ if ( popup . closed ) {
128+ clearInterval ( checkClosed ) ;
129+ window . removeEventListener ( 'message' , handler ) ;
130+ reject ( new Error ( 'Login cancelled' ) ) ;
131+ }
132+ } , 1000 ) ;
133+ } ) ;
134+ }
135+
136+ /**
137+ * Performs silent logout by loading the token authentication logout service URL in an
138+ * invisible iframe.
139+ *
140+ * @param {string } tokenAuthLogoutServiceUrl - Logout service URL.
141+ * @returns {Promise<any> } A promise that resolves when logout is complete.
142+ */
143+ export function silentLogout ( tokenAuthLogoutServiceUrl : string ) : any {
144+ return new Promise < void > ( resolve => {
145+ const iframe = document . createElement ( 'iframe' ) ;
146+
147+ iframe . style . display = 'none' ;
148+ iframe . src = tokenAuthLogoutServiceUrl ;
149+ document . body . appendChild ( iframe ) ;
150+
151+ let timerId : any = undefined ;
152+
153+ // Listen for logout completion
154+ // @ts -ignore
155+ const handler = event => {
156+ if ( event . origin !== window . location . origin ) return ;
157+
158+ if ( event . data . type === 'logout-success' ) {
159+ window . removeEventListener ( 'message' , handler ) ;
160+ document . body . removeChild ( iframe ) ;
161+
162+ timerId && clearTimeout ( timerId ) ;
163+
164+ resolve ( ) ;
165+ }
166+ } ;
167+
168+ window . addEventListener ( 'message' , handler ) ;
169+
170+ // Fallback timeout
171+ timerId = setTimeout ( ( ) => {
172+ window . removeEventListener ( 'message' , handler ) ;
173+ if ( iframe . parentNode ) {
174+ document . body . removeChild ( iframe ) ;
175+ }
176+ resolve ( ) ; // Assume success after timeout
177+ } , 3000 ) ;
178+ } ) ;
179+ }
180+
49181/**
50182 * Opens token auth URL page.
51183 *
@@ -63,6 +195,42 @@ export function openTokenAuthUrl(tokenAuthServiceUrl: string): any {
63195 }
64196 } ;
65197
198+ if ( ! browser . isElectron ( ) && isTokenAuthInline ( getState ( ) [ 'features/base/config' ] ) ) {
199+ loginWithPopup ( tokenAuthServiceUrl )
200+ . then ( ( result : { accessToken : string ; idToken : string ; refreshToken ?: string ; } ) => {
201+ // @ts -ignore
202+ const token : string = result . accessToken ;
203+ const idToken : string = result . idToken ;
204+ const refreshToken : string | undefined = result . refreshToken ;
205+
206+ // @ts -ignore
207+ dispatch ( setJWT ( token , idToken , refreshToken ) ) ;
208+
209+ logger . info ( 'Reconnecting to conference with new token.' ) ;
210+
211+ const { connection } = getState ( ) [ 'features/base/connection' ] ;
212+
213+ connection ?. refreshToken ( token ) . then (
214+ ( ) => {
215+ const { membersOnly } = getState ( ) [ 'features/base/conference' ] ;
216+
217+ membersOnly ?. join ( ) ;
218+ } )
219+ . catch ( ( err : any ) => {
220+ dispatch ( setJWT ( ) ) ;
221+ logger . error ( err ) ;
222+ } ) ;
223+ } )
224+ . catch ( err => {
225+ dispatch ( showErrorNotification ( {
226+ titleKey : 'dialog.loginFailed'
227+ } ) ) ;
228+ logger . error ( err ) ;
229+ } ) ;
230+
231+ return ;
232+ }
233+
66234 // Show warning for leaving conference only when in a conference.
67235 if ( ! browser . isElectron ( ) && getState ( ) [ 'features/base/conference' ] . conference ) {
68236 dispatch ( openDialog ( 'LoginQuestionDialog' , LoginQuestionDialog , {
0 commit comments