diff --git a/include/mix.h b/include/mix.h index c5964087..fd98ff0f 100644 --- a/include/mix.h +++ b/include/mix.h @@ -137,6 +137,8 @@ void slmix_session_video(struct session *sess, bool enable); int slmix_session_speaker(struct session *sess, bool enable); int slmix_session_new(struct mix *mix, struct session **sessp, const struct http_msg *msg); +int slmix_session_auth(struct mix *mix, struct session *sess, + const struct http_msg *msg); int slmix_session_start(struct session *sess, const struct rtc_configuration *pc_config, const struct mnat *mnat, const struct menc *menc); diff --git a/src/http.c b/src/http.c index a3cf3f61..233368eb 100644 --- a/src/http.c +++ b/src/http.c @@ -178,6 +178,24 @@ static void http_req_handler(struct http_conn *conn, return; } + if (0 == pl_strcasecmp(&msg->path, "/api/v1/client/reauth") && + 0 == pl_strcasecmp(&msg->met, "POST")) { + err = slmix_session_auth(mix, sess, msg); + if (err == EAUTH) { + http_sreply(conn, 401, "Unauthorized", "text/html", "", + 0, NULL); + return; + } + if (err) + goto err; + + slmix_session_user_updated(sess); + slmix_session_save(sess); + + http_sreply(conn, 204, "OK", "text/html", "", 0, sess); + return; + } + if (0 == pl_strcasecmp(&msg->path, "/api/v1/client/name") && 0 == pl_strcasecmp(&msg->met, "POST")) { diff --git a/src/sess.c b/src/sess.c index 2ca65969..8be9390f 100644 --- a/src/sess.c +++ b/src/sess.c @@ -245,48 +245,55 @@ static int slmix_session_create(struct session **sessp, struct mix *mix, } -int slmix_session_new(struct mix *mix, struct session **sessp, - const struct http_msg *msg) +int slmix_session_auth(struct mix *mix, struct session *sess, + const struct http_msg *msg) { struct pl token = PL_INIT; - bool speaker = false, host = false; - int err; re_regex((char *)mbuf_buf(msg->mb), mbuf_get_left(msg->mb), "[a-zA-Z0-9]+", &token); - if (token.l > 0) { if (str_isset(mix->token_host) && 0 == pl_strcmp(&token, mix->token_host)) { info("sess: host token\n"); - speaker = true; - host = true; + sess->user->host = true; + sess->user->speaker = true; } else if (str_isset(mix->token_guests) && 0 == pl_strcmp(&token, mix->token_guests)) { info("sess: guest token\n"); - speaker = true; + sess->user->host = false; + sess->user->speaker = true; } else if (str_isset(mix->token_listeners) && 0 == pl_strcmp(&token, mix->token_listeners)) { info("sess: listener token\n"); - speaker = false; + sess->user->host = false; + sess->user->speaker = false; } else { return EAUTH; } } - err = slmix_session_create(sessp, mix, NULL, NULL, NULL, host, - speaker); + return 0; +} + + +int slmix_session_new(struct mix *mix, struct session **sessp, + const struct http_msg *msg) +{ + int err; + + err = slmix_session_create(sessp, mix, NULL, NULL, NULL, false, false); if (err) return err; - return 0; + return slmix_session_auth(mix, *sessp, msg); } diff --git a/tests/bdd/environment.py b/tests/bdd/environment.py index aff6c2b6..457a542f 100644 --- a/tests/bdd/environment.py +++ b/tests/bdd/environment.py @@ -1,6 +1,6 @@ -import subprocess import time import socket +from subprocess import Popen def is_port_reachable(host, port): @@ -28,7 +28,7 @@ def before_all(context): context.ws = {} context.sessid = {} context.response = {} - context.proc = subprocess.Popen(["./build/slmix", "-c", "config_example"], cwd="../../.") + context.proc = Popen(["./build/slmix", "-c", "config_example"], cwd="../../.") wait_until_port_reachable('127.0.0.1', 9999, 1) diff --git a/tests/bdd/login.feature b/tests/bdd/login.feature index e0507226..45c95114 100644 --- a/tests/bdd/login.feature +++ b/tests/bdd/login.feature @@ -5,6 +5,20 @@ Feature: Login/Logout Then "Alice" WebSocket receives "1" users And "Alice" closes WebSocket + Scenario: WebSocket Session Login re-auth + Given "Alice" connects without a token + And "Alice" set client name + Then "Alice" WebSocket receives "1" users + And "Alice" WebSocket receives rooms update + Given "Alice" reauth with token "TOKENHOST" + Then "Alice" WebSocket receives updated "Alice" user + And "Alice" closes WebSocket + + Scenario: WebSocket Session Login with token host + Given "Alice" connects with token "TOKENHOST" + Then "Alice" WebSocket receives "1" users + And "Alice" closes WebSocket + Scenario: Login as a audience user Given "Alice" connects without a token And "Alice" set client name diff --git a/tests/bdd/steps/steps.py b/tests/bdd/steps/steps.py index 8b76cddc..b681ff64 100644 --- a/tests/bdd/steps/steps.py +++ b/tests/bdd/steps/steps.py @@ -12,6 +12,27 @@ def step_impl1(context, user): "Session-ID"] +@given('"{user}" connects with token "{token}"') +def step_impl1(context, user, token): + response = requests.post(context.base_url + '/api/v1/client/connect', + data=token) + assert response.ok, f'Error: {response}' + context.sessid[user] = response.headers['Session-ID'] + assert response.headers['Session-ID'] is not None, response.headers[ + "Session-ID"] + + +@given('"{user}" reauth with token "{token}"') +def step_impl1(context, user, token): + headers = {'Session-ID': context.sessid[user]} + response = requests.post(context.base_url + '/api/v1/client/reauth', + headers=headers, + data=token) + assert response.ok, f'Error: {response}' + assert response.headers['Session-ID'] is not None, response.headers[ + "Session-ID"] + + @then('"{user}" WebSocket receives "{count}" users') def step_impl2(context, user, count): ws = create_connection("ws://127.0.0.1:9999/ws/v1/users") @@ -74,11 +95,13 @@ def step_impl8(context, user, updated_user): assert resp["name"] == updated_user, f'name: {resp}' -@then('"{user}" WebSocket receives rooms update') -def step_impl8a(context, user): +@then('"{user}" WebSocket receives updated "{updated_user}" user') +def step_impl8(context, user, updated_user): response = context.ws[user].recv() resp = json.loads(response) - assert resp["type"] == 'rooms', f'type: {resp}' + assert resp["type"] == 'user', f'type: {resp}' + assert resp["event"] == 'updated', f'event: {resp}' + assert resp["name"] == updated_user, f'name: {resp}' @then('"{user}" WebSocket receives "{delete_user}" delete') @@ -90,6 +113,13 @@ def step_impl9(context, user, delete_user): assert resp["name"] == delete_user, f'user name: {resp}' +@then('"{user}" WebSocket receives rooms update') +def step_impl8a(context, user): + response = context.ws[user].recv() + resp = json.loads(response) + assert resp["type"] == 'rooms', f'type: {resp}' + + @then('"{user}" logouts') def step_impl10(context, user): headers = {'Session-ID': context.sessid[user]} diff --git a/webui/src/api.ts b/webui/src/api.ts index 5fdec5ce..eadf319e 100644 --- a/webui/src/api.ts +++ b/webui/src/api.ts @@ -7,7 +7,6 @@ import { Error } from './error' interface Session { id: string auth: boolean - host: boolean user_id: string | null } @@ -55,6 +54,13 @@ export default { return false }, + async reauth(token: string | string[]) { + if (!token) + return + + await api_fetch('POST', '/client/reauth', token) + }, + async connect(token?: string | null) { if (!token) token = window.localStorage.getItem('token') @@ -71,7 +77,7 @@ export default { } /* Readonly! Use ws/users for updated states */ - sess = { id: session_id, auth: false, host: false, user_id: null } + sess = { id: session_id, auth: false, user_id: null } window.localStorage.setItem('sess', JSON.stringify(sess)) }, @@ -88,7 +94,6 @@ export default { const user = JSON.parse(await resp?.text()) sess.user_id = user.id - sess.host = user.host window.localStorage.setItem('sess', JSON.stringify(sess)) router.push({ name: 'Home' }) @@ -143,12 +148,8 @@ export default { else await api_fetch('PUT', '/webrtc/audio/disable', null) }, - is_host(): boolean { - return sess.host - }, - async record_switch(type: RecordType) { - if (!sess.host) return + if (!Users.host_status.value) return if (Users.record.value) { Users.record.value = false diff --git a/webui/src/components/Listeners.vue b/webui/src/components/Listeners.vue index b4263fce..e3e924a9 100644 --- a/webui/src/components/Listeners.vue +++ b/webui/src/components/Listeners.vue @@ -52,7 +52,7 @@