From 176ab4a1944b8072bd143f102ae562efaa0081a8 Mon Sep 17 00:00:00 2001 From: Kobe Date: Mon, 9 Jun 2025 15:07:29 +0200 Subject: [PATCH] updated authentication of instances --- __pycache__/app.cpython-313.pyc | Bin 10288 -> 11253 bytes __pycache__/models.cpython-313.pyc | Bin 37357 -> 37475 bytes .../versions/761908f0cacf_merge_heads.py | 24 +++ migrations/versions/add_connection_token.py | 24 +++ migrations/versions/add_status_details.py | 14 +- models.py | 1 + routes/__pycache__/main.cpython-313.pyc | Bin 85748 -> 87061 bytes routes/admin_api.py | 21 ++- routes/main.py | 21 +++ templates/main/instances.html | 137 ++++++++++++++++++ 10 files changed, 240 insertions(+), 2 deletions(-) create mode 100644 migrations/versions/761908f0cacf_merge_heads.py create mode 100644 migrations/versions/add_connection_token.py diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc index 0244db845e75700f226a8997045d0d237bc528af..6ca4e8a9c035defd40822643a9d1d02c813b7b9f 100644 GIT binary patch delta 1882 zcmZ{kZA@EL7{~AJ>+S73+(KU{eOqY@V^Am?bQ9brgS`NwceprIR_PtzZ3p*RZ)&cJwJHe3@GM@J6U2)ubX#mARp`FB8mN_6 zb2OkWlDcIzDrp3Du82Xi67_N}TbSJ-TH7qHD*aID84X{S5 zY_ob*1?2C`4CuN{19SIfOi7O_K(fVV;t$qyR3oneL!TTU zU_|bEY{?sP59Dvi4d|h~2-H7?coT)=G0KNtp^Q1T6V_dix~XFH2W1Am5&2bSggWZyId3*C5YaZyo2?yZr02C*h;nvzH8a)CW?pZ zP#a#Obls{e(&as&cHE_EXFgsTd+7hKCp++dpNG5d${X04Rpl$$+9ziHPt4Y>%66k( zg=?#74dEbqL-`(ZB5WiGUb(7keUDN@)QeV}HiCtU&B{*C(ui&=+)7yIDY1<>K>7lc z3yN!Mr4p7bhG$VELYGUwP$*ahOS8%*3UwPz@E1C4ye3vRkmxs~PpnxUwM(N`x6*7t z;?pCW$xCKYyQz$vK%A+8Q*VyMqI@)w6vD~4kWoAn?;qYhJec6J40|k*DVJP}Ps<2;%+m6$a{0(XED z1TNC|a+Euoq52Po`2jA*WzoFJZ_O6v8rl1yfa8M1uCb7dgyh zkJ8h{o)qOR1RGLRV&&0qhY3EE5>7V!Vd&I>rf% z5y0{4yGpy05Z}cA<8EO1}9zs_|)jb zvO3e7I6j539|QjnNT>mJbg!&jc>xr>c0_^|Ps**eB5kSEwfL z+QTTkF$SN4ngZcKP68f%9yc-J{*qqv(QdRu5fuJq(&FC##bz2k2 zl)^mUOsKRccVrXz0`IOTyF}uW?oM{n*{z*i4 z>E*w_kM=v-P_x}_`3U?IqKxzayuyct@m#cYx8vx9-7ftEGB?l_dyKq@^bUjaD#XIa z7?+XXVWB?-`3(-Qpl2N+$3>9GweTB=#rc{*t$*FYWNc9Q3>Pe$?l{Wn35dU4GA%el z?T?vimrS3pm_Q{&0J~kNgdwARDIDn&xFf?H{B*yB%q@6e2k$u3u+4}oAiIPU<7nKq zUWU&ke1$TuM(ItE-=Weqe@iDp{fHR%Ch`*Mb_dAQlc(KslDvT4t0-NA&n)1B33#Kq j1_HJUXECrJT+2O~sv*a$s zX4TXOAJmvuIIY%bZCb6?Xo3xiKJ-OjOMNibh?~Yp(!@0J0Yu}^i=MmFBG#MSbHDSQ zbG|v}-udmZPY?PwyzV}7HjIM}#GmYp;9ljI|s>7PN3+;wCsAa?? zMl-I%6&D6v9ZmpVarGklF&KpJT;AK$cs3bLu`XK|VT>&^wLy%HZSTLsW zNscSccS$ZON#vK55d5svAo_dD?^C(Ipqg+@t*fA6)a`~x)d2jWMi4)+7>a=DZl@2y zH(K)^y@fQA(cO3EP)N3p5L+AFbBDa3ULnuYzg;U5_v3B?*wm=u4 zzkVJb-k_gEfHy^op9{;Wcv`5gk%s7|NaV!2z~QfLU^g{I zB=-=#>G7T1|LXqN9=YLZx$bHG-A%M8wz98z*PxE@x;wI7MLhl&AO5SDM#Zb$bVL@f z_87yB!b+nyB1$VeeZ&3IO0R_cO2356HBm%-P1Z(2%B@rIMPdlVmc;JYvHT;UEKSU0 zi$@t$Jg0e%ivpT>oVDRm_RVG|tuebWH#Sk2w{z@GBmt&c@`H@8&3DQ@0C^B*bqM1eg$oy%D^dmCeCc+we&CJr^;CDraoy~7h6DL9d6(B4J= ztW)&a|BKR2(Yb9!W^2869{meW(ZSYLx(H^nQ$EcJfbnFv%m-%g!MS9Q^Z~*T;YRWg z=@OzP_t*i>jAf6L1AmOU0@1&04qZUzy*lgdbpx5)S>85d(Oal^$} Z2~M;%(JOGd?Q!KlIZC>9#jcKw&p4jBKU0j^Z!OX$zmdr(> zlQ)Vty3BF@8Z)MvzVhhF~F}yac*D2TXplqpbMk35+ZxpGV)cE)ZHSxkz$_(PG&RB?nS2 oihEyR5!o!)H=B|1;^tlb3QWR6jG7%q{S}=RUm1YRB2%E90H{<``2YX_ delta 203 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 d_Wnnq(shT_tel$v7qPU{|A0ql)wN0 delta 4387 zcmbVOeQ;FO72mU)eY>08d;q&7Y&Ic~5PgLJ%0~c0Ohf`0LVyJd+7bh7AiBwhyBp<0 zjTUQb8C1BHhtifW+c6*@eI3$Tr$eMg2c+UCg*+96VWvbF>Bu)D(EGbD$;JRq>-*#P z&b{}1-E+@9=f3);{lahU$>)=j5-sAp{7&g2Z|bh(Z3)h`E{kPu&fHwyl5n1K`7R}m zXMO78hwVPf)z8?+k(eCf@FxU)wK;r)V;JS|PaGozx}5!Ny@P2y-;OCEd270;(kLD*_z4&HNt2dZ(i{=t@F{zKYLqiwI zadPow(sPc57Ios3`4*mZAzgnj**lL^{Yd@$33OH8(D0y*uJTh$zSgUs`6h*&`ka@p z(@eK;l{o<(z`up#ED2OHz$HL+GB4=w=j;JaJDUo5r@rl#Swt>wT{D54{Gm1X`r{h9 zo8fEM@ZNoewBf3*35P1{t73~-3Jj+5`}sU-(>L?}3eD&Lc0ydM6-8q|1 z>1%iTt>oalcBwvTG8G!!fa8Fl@N>InD3`4kOLPHWzq43>@sATJS#knqs8;6S$Mr2_ z(^vfIuOu|xzk9g*Mi=>Kc26OYjd-z%yDZQW^Ff1IPm_}{5LH_X)-_9au1dBn=P4Xh?!xpuUZ(2%9A#1(NK&{<-^`9HUpB*m%jJ6iBCZK zNkG1EM@?Z%K#vf#;h7()t*@$ET3H=owQMowr30pI43`4h)2MN;Us;B6jBnUG-jOEK zExN?U_Z;);H}_UJ@>gSR4S)lF4R{r>7N7xOVp&eGe8yD1xGgJdFms5BYe}_LHFbl9 z^Rb$`g;7>7=2T*Jy!pTwx7@p#E&SU9MYNdT_u)*c)>nVnY|DoovFm7`w%%OGMb+kl{ zz#VJ*yr7#BWSuZv4bc#*tgEUGF&_lec=YH8|RY=W(=0*us-bMA<->S51f^*O-L04o5`1AY#80q_gJF99zCekH*0 zgd+Pn7hN2iwNEH&L+4bjh7yj(SWUfQ zk2TbXLn3qc^Su{?qRggUdXx6?J(sGeTetj*;vGxnstzMzeO;S#JagH<52=-(*Z%iNTI^; z#3He(aAoL;P)c1aY8Go5fu-8FZptR6WqQP~vAM`YuQ;atfH1?9Y_MlQ$u28nK3fb` zMSDAgyxK4?dCitl1}2nkHNYDW`EH3)SVl>Ppco;L`;-lG2Zr$*at?~{xDX}ce+&jY z+hN>1yj5D3k4hc;#Px{wweqna=Qh9WqYQ;+Yuhr&Pv^9K8T9(VJ6gYhZP*Q`2yR^r zJ$jY4GCKw@Cl?JZu+NgXQqL?bQ+I*ViXjjLJS_AEE zN;{rUex(P@^wMtT(_E@)o;#Sl&cR{mkkzyn)E>mB+oMvJ0V!LkZ5&Cesb{SA(MSqp z#GjMM@foZf3filSQb5^_F{nj;GnwhqK)kw0wX(!41BQA&N>(mQzqPVGi1r7x8CPF^)9 zWaNF8+mwOsg}FyyLOSlkP~Pdp?g!EBPRo=`1iRFotvx+~CKEO9pFnk1UjWH~_l)7J zt$IAPpoT@&0b2P(ba&%57`q9uf@u(7C}0?1Bw!L?4qz^z3Q!HG0lWlw9q@a=4!|zJ z9>8gU>_z_pbq(N#n_fVSkSzT`s(^u5{zNzv9ia6nqdaX?ndt6A#8NgUPuiPjm(gl! z&MK#{o!-?}PNzIKiWnP&9oV&Zr&AVf&_0_^73u&elWi9Y*hFv?0!9lkoQ1WK1r6a) S5i8MxLHdiUI-L^b`hNi^nE4R^ diff --git a/routes/admin_api.py b/routes/admin_api.py index 6688902..0a8b606 100644 --- a/routes/admin_api.py +++ b/routes/admin_api.py @@ -1,4 +1,4 @@ -from flask import Blueprint, jsonify, request, current_app +from flask import Blueprint, jsonify, request, current_app, make_response from functools import wraps from models import ( KeyValueSettings, User, Room, Conversation, RoomFile, @@ -13,6 +13,25 @@ import secrets admin_api = Blueprint('admin_api', __name__) +def add_cors_headers(response): + """Add CORS headers to the response""" + response.headers['Access-Control-Allow-Origin'] = '*' + response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS' + response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, X-API-Key, X-CSRF-Token' + return response + +@admin_api.before_request +def handle_preflight(): + """Handle preflight requests""" + if request.method == 'OPTIONS': + response = make_response() + return add_cors_headers(response) + +@admin_api.after_request +def after_request(response): + """Add CORS headers to all responses""" + return add_cors_headers(response) + def token_required(f): @wraps(f) def decorated(*args, **kwargs): diff --git a/routes/main.py b/routes/main.py index 9d05298..89cba90 100644 --- a/routes/main.py +++ b/routes/main.py @@ -482,6 +482,27 @@ def init_routes(main_bp): return jsonify(status_info) + @main_bp.route('/instances//save-token', methods=['POST']) + @login_required + @require_password_change + def save_instance_token(instance_id): + if not os.environ.get('MASTER', 'false').lower() == 'true': + return jsonify({'error': 'Unauthorized'}), 403 + + instance = Instance.query.get_or_404(instance_id) + data = request.get_json() + + if not data or 'token' not in data: + return jsonify({'error': 'Token is required'}), 400 + + try: + instance.connection_token = data['token'] + db.session.commit() + return jsonify({'message': 'Token saved successfully'}) + except Exception as e: + db.session.rollback() + return jsonify({'error': str(e)}), 400 + UPLOAD_FOLDER = '/app/uploads/profile_pics' if not os.path.exists(UPLOAD_FOLDER): os.makedirs(UPLOAD_FOLDER) diff --git a/templates/main/instances.html b/templates/main/instances.html index 8da9ff8..642f4ed 100644 --- a/templates/main/instances.html +++ b/templates/main/instances.html @@ -43,6 +43,7 @@ Payment Plan Main URL Status + Connection Token Actions @@ -64,6 +65,19 @@ {{ instance.status|title }} + + {% if instance.connection_token %} + + Authenticated + + {% else %} + + + Generate Token + + + {% endif %} +

+ + + {% endblock %} {% block extra_js %} @@ -181,11 +226,13 @@ let addInstanceModal; let editInstanceModal; let addExistingInstanceModal; +let authModal; document.addEventListener('DOMContentLoaded', function() { addInstanceModal = new bootstrap.Modal(document.getElementById('addInstanceModal')); editInstanceModal = new bootstrap.Modal(document.getElementById('editInstanceModal')); addExistingInstanceModal = new bootstrap.Modal(document.getElementById('addExistingInstanceModal')); + authModal = new bootstrap.Modal(document.getElementById('authModal')); // Initialize tooltips const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); @@ -409,5 +456,95 @@ async function submitAddExistingInstance() { alert('Error adding instance: ' + error.message); } } + +function showAuthModal(instanceUrl, instanceId) { + document.getElementById('instance_url').value = instanceUrl; + document.getElementById('instance_id').value = instanceId; + document.getElementById('authForm').reset(); + authModal.show(); +} + +async function authenticateInstance() { + const form = document.getElementById('authForm'); + const formData = new FormData(form); + const instanceUrl = formData.get('instance_url').replace(/\/+$/, ''); // Remove trailing slashes + const instanceId = formData.get('instance_id'); + const email = formData.get('email'); + const password = formData.get('password'); + + try { + // First login to get token + const loginResponse = await fetch(`${instanceUrl}/admin/login`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + body: JSON.stringify({ email, password }) + }); + + if (!loginResponse.ok) { + const errorData = await loginResponse.json().catch(() => ({})); + throw new Error(errorData.message || 'Login failed'); + } + + const { token } = await loginResponse.json(); + + // Then create management API key + const keyResponse = await fetch(`${instanceUrl}/admin/management-api-key`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ + name: `Connection from ${window.location.hostname}` + }) + }); + + if (!keyResponse.ok) { + const errorData = await keyResponse.json().catch(() => ({})); + throw new Error(errorData.message || 'Failed to create API key'); + } + + const { api_key } = await keyResponse.json(); + + // Save the token to our database + const saveResponse = await fetch(`/instances/${instanceId}/save-token`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': formData.get('csrf_token') + }, + body: JSON.stringify({ token: api_key }) + }); + + if (!saveResponse.ok) { + const errorData = await saveResponse.json().catch(() => ({})); + throw new Error(errorData.message || 'Failed to save token'); + } + + // Show success and refresh + authModal.hide(); + location.reload(); + } catch (error) { + console.error('Authentication error:', error); + alert('Error: ' + error.message); + } +} + +function copyConnectionToken(button) { + const input = button.previousElementSibling; + input.select(); + document.execCommand('copy'); + + // Show feedback + const originalText = button.innerHTML; + button.innerHTML = ''; + setTimeout(() => { + button.innerHTML = originalText; + }, 2000); +} {% endblock %} \ No newline at end of file