From 69e6d0725eaef951f783ba1ea37bcaf9f333e3e9 Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Wed, 16 Jul 2025 21:08:27 +0100 Subject: [PATCH 01/26] Working v1 --- README.md | 2 +- UnitystationLauncher/Assets/buttoncontext.png | Bin 0 -> 2026 bytes .../org.unitystation.StationHub.metainfo.xml | 1 - UnitystationLauncher/Assets/userbg.png | Bin 0 -> 1558 bytes UnitystationLauncher/Assets/userico.jpg | Bin 0 -> 8071 bytes .../Models/AuthenticationStuff.cs | 111 ++++++++ UnitystationLauncher/Models/User.cs | 7 + UnitystationLauncher/Services/ApiServer.cs | 158 +++++++++++ UnitystationLauncher/Services/AuthService.cs | 260 ++++++++++++++++++ .../OfficialCentralCommandAuthentication.cs | 56 ++++ UnitystationLauncher/StandardModule.cs | 3 +- .../ViewModels/ForgotPasswordViewModel.cs | 72 +++++ .../ViewModels/LoginStatusViewModel.cs | 180 ++++++++++++ .../ViewModels/LoginViewModel.cs | 106 +++++++ .../ViewModels/MainWindowViewModel.cs | 104 ++++++- .../ViewModels/SignUpViewModel.cs | 151 ++++++++++ .../Views/ForgotPasswordView.xaml | 48 ++++ .../Views/ForgotPasswordView.xaml.cs | 18 ++ .../Views/LoginStatusView.xaml | 56 ++++ .../Views/LoginStatusView.xaml.cs | 18 ++ UnitystationLauncher/Views/LoginView.xaml | 56 ++++ UnitystationLauncher/Views/LoginView.xaml.cs | 18 ++ UnitystationLauncher/Views/SignUpView.xaml | 89 ++++++ UnitystationLauncher/Views/SignUpView.xaml.cs | 18 ++ 24 files changed, 1524 insertions(+), 8 deletions(-) create mode 100644 UnitystationLauncher/Assets/buttoncontext.png create mode 100644 UnitystationLauncher/Assets/userbg.png create mode 100644 UnitystationLauncher/Assets/userico.jpg create mode 100644 UnitystationLauncher/Models/AuthenticationStuff.cs create mode 100644 UnitystationLauncher/Models/User.cs create mode 100644 UnitystationLauncher/Services/ApiServer.cs create mode 100644 UnitystationLauncher/Services/AuthService.cs create mode 100644 UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs create mode 100644 UnitystationLauncher/ViewModels/ForgotPasswordViewModel.cs create mode 100644 UnitystationLauncher/ViewModels/LoginStatusViewModel.cs create mode 100644 UnitystationLauncher/ViewModels/LoginViewModel.cs create mode 100644 UnitystationLauncher/ViewModels/SignUpViewModel.cs create mode 100644 UnitystationLauncher/Views/ForgotPasswordView.xaml create mode 100644 UnitystationLauncher/Views/ForgotPasswordView.xaml.cs create mode 100644 UnitystationLauncher/Views/LoginStatusView.xaml create mode 100644 UnitystationLauncher/Views/LoginStatusView.xaml.cs create mode 100644 UnitystationLauncher/Views/LoginView.xaml create mode 100644 UnitystationLauncher/Views/LoginView.xaml.cs create mode 100644 UnitystationLauncher/Views/SignUpView.xaml create mode 100644 UnitystationLauncher/Views/SignUpView.xaml.cs diff --git a/README.md b/README.md index aaa2d9ed..fbb32c36 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Flathub](https://img.shields.io/flathub/v/org.unitystation.StationHub?style=flat-square)](https://flathub.org/apps/details/org.unitystation.StationHub) [![Discord](https://img.shields.io/discord/273774715741667329?style=flat-square)](https://discord.com/invite/tFcTpBp) -StationHub is the official launcher for Unitystation, it handles downloading, updating, and joining servers. +This is the official launcher for Unitystation, it handles account creation, downloading, updating, and server joining. ## Tech-stack diff --git a/UnitystationLauncher/Assets/buttoncontext.png b/UnitystationLauncher/Assets/buttoncontext.png new file mode 100644 index 0000000000000000000000000000000000000000..b15a09331f949f53a63cc3535a5c85ea68ec108d GIT binary patch literal 2026 zcmbVMX*3(?8crCD-Jn%LIf`m6T~L!)qV_0}qP16Rt5ADvR|z`Q7Dj1NWo%PRgkEu} zq_!fswFRjqjdZH^64EFV?4IzdA1TQ=>@#}A<5lOsqR6FHR2{|1h-D0m{@Y2vR_mDD`FeI4L#Pv<^V-aBNnL* zgmms)_`R=LnFEfFS9wQGK9>;;vv!RH0EDHE0|=c(2K*8)R1QvNf!h-tsQhGQp zC5y6fL79aH2m6Jf0A>+>cTj%5N-;=OfRYu=4&j_ECIbNQ^}x)pI>pXy%+W#}qvQv+ z#cKr%UPeW%&NZ6lDaHSwtIRv)E`=dmXHy}B=mrI2&)75i9)qc5;e}J;K0Zk*X?;)= zZx-oS@=mg7is2CZWirXUYKHICRtn1dRHG-lW`p&$(}OrU={&l7FbbwhQf;hmPF&8* z@PFcc`-%yWPa44^jW7{Jm;hD(h6(`O^k2De=ve$s`4>92@_h&WpJo5s1ebk!c-gLX z@y*Wafa2QY!dp`~Y8xgY7E+oA-7E8!TGl$M#My~LW{xrfgB0~*bhTaUjpCsb-4$o> zP@N(Hj0-h^?7`Jq-Y}04vsu#=zvVGtrDQaiBy*>xR`!E~ zLBdv#i#yBcMdA$o&ZqVyDdrwW7qSbU90sEr&rmWc^hMOlU`3*yz9E>A7suL4@_;`i zkJ;Dga3``DlBKBm}xWZB>@NO8ZNgbt#rQD!zs15n+Q z`va;J(pxnJxw#85rPyaaA&DQag;x`vi76NvUNkt=!mOY7AJ#H3jkNRZstD73F4Njt zSO4#K{aqadx02uD+}c9u6Z1wyt@Z#q8NZ$=vk|9I5eJobwYJBFzTN5>%g(JnVaL6} z=6&p`;n$~gr19*M)9A^ONCQ{I6X@+x+V$Tji!=LdYjl__>CUHxqPj5*`l>hx&2qk% z;cKHn17gxDNv*EU(B$m2O6FdQjhcqfLH;6D@bINEAI;NXRf$mn*SUgVQ<>0B&%_Pa8g5P~Q$KyX+2%LxgG4qS+n4?Z{-J|Td z{^a=%b_v8`A)^{{KbcEdq#SmzWzcZU*+*--_XS$iuSUuo^=G5G&ulw-&X0 z?gp{Vx$uIQ*2=E-EkZ+dXp?G;Nft;q22PtN;~c%vCDWWP$PVUK{G@v`mY9iaWS9 znl|pmEQ>QzLby^qzSsVly*N!j@24*#zM_mwLkk{tY1$-7mM8HDsRM=7%YRTW|AuUi viSMCf@&A;6T6wAdds links to the Unitystation Discord and GitHub issues pages.
  • The auto update feature has been replaced with a notification.
  • Adds a placeholder to the News panel, this will be replaced with posts from the Unitystation Blog in the future.
  • -
  • Account creation, management, and login is now handled by the game instead of StationHub.
  • Bug Fixes:

      diff --git a/UnitystationLauncher/Assets/userbg.png b/UnitystationLauncher/Assets/userbg.png new file mode 100644 index 0000000000000000000000000000000000000000..2e27f6662927ee444c9df8f3a6c3957bfb6ce822 GIT binary patch literal 1558 zcmdT^O-~a+7#<}-!bkkzq!%WOF&gcD^b2;SLg}_r<02_d=)nWa?v!p|ch=p3mc|64 z4eC)Z#+$!@lLwUp(RffUUJYspdeDOj$caQ_oGoo3rp2Ri53@7xzR&YM^UOPYZ)Es# zN9(Cp06<4_NKOH8c%IHnEzNY?C@C%Ub&|w0WE5vfUUd+N>UaXdq^VA#6jJrM8*fk_ z08M9%bcSS>wCo z!#+(2hG37#3gJLd6fZzOFZ6PJFDC?iLRbp&lHU(24@0XtdR9uwiApWHiZD4sY>DIY z`8=Bsu-KX81W^<@-p~2{KAPck=PaTYe3si?Wss4pIfhLPY(bAvoxn3B!ce8VTQKb! zt>sp1qK0t=)#e12_qJ3`RFs2LO|zEmk`&t0TbI~P&)JAeAs5d$8a=pdw^z!RVh&OX zcG4J6S6v**VS?QpwqY#hxhBH1V}_;UynC*UQ4}d@xkR-zl$0Y3Wnm3Nmt;N~ltXfe z?+MF-5RVRq{PJLLA`lIR{9+&;s$ylV&6vm{Rjj^;4L8Jk?O@upvWy&K7U>BGo3OlX z$!M%4(2%c!)f;OGHpFsN4Cfv0zQeADXa{-ITIfh*0c!#5WnZ+uL$AD$Pt-JPZPO<-k&-d LAC{K}$8Y@xu|n?# literal 0 HcmV?d00001 diff --git a/UnitystationLauncher/Assets/userico.jpg b/UnitystationLauncher/Assets/userico.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f90445ba521756adb7030590a4da1a75e9af0efe GIT binary patch literal 8071 zcmeHLcUTi?w;!a42q;Y{f*6zzAqj*USfvXAl~4r{LMWjpp*MF$=_0*@!cr2NfS?Fi zAb@lck*Wwvm(J2exPi~I*XP@NFW=wyeV$CtJ2}78<~MWBWVe5J6yQ|%cCZBiZEaD2 z9{9O-KLadk9%x4&fC`|YoK^yW-BD_=J%Qk+00z6@C9P~+tuc~lS7)%dl^Yl$DFp_U zRJ`4+(2f`azct3r!360qUCKsD$4vy z-U{B%Zq68j6~DK$lM7zK8v)uku0WCZPJ==G`zi!S1nB&pSAG-ZC4N;`9EM*;5-x#; zNXzic%1c7vFll*taek;2L=G$^2Zq2TAaDg~DFrB$|Jwplyy0wY6|SgheDg)wAwb`f z^78VM^nyvc;_Sc>d3kxT6ch}FN>DT;@IEdCD{l!Gyuf!0Y8X5k=io+gaCPC|vuI`Q z>Onw&D3Sgv1!uP(X8&!i{-%gT{?Dq;&U*#eH;X4+!TgN(-x|iFeB3bLD;T`12M&#) zs0-|Y-4s-D7%PG+4(00V^u3Cg>|F`2czahjepS`Is>$;U8#}nzxO(A5_AyALg0>5u zVC90wXsaPW6f2Sr4mJvK8Fe*T4XBKiEL;@=Q9myyBQGT<1yh5=FTi9k$jW`ks=1;) zoG~th?^v6kSol9;_vqm4M)9nM!8v$hY&3AL&iwmnD>(dfEUoGQin>lJI zg3O7Nm)(DlxetP}mt7IKV2%3&MxU*Ut~kr_VieCpwP%R-$WJ#^hiF)++0UG}`W*_n z?0)~nDC-4lS~gJ36#>}pAizk8zcM zSJpROX@6wUWLN+4lEN+kd&aFS_-tkJvyXR1P|8TuX*SEvl{KYWy=UzUP0?Yc+nZnb zt_fLAczx6)_wD$9jRv!ot|%_$j5+?lv;Vb>K9yfc@hNcKuU$-8EN3R%d=kz2B&sGA zmorTVPSZD^tWxjV(&N0oD6Xn#)_lx2NZ=8CU_tNM*2=0$THK8vMEylL8Wwp&tB^XC zn4vVM^?b{(K3n3*mgm2qer%~^gZwg&*}J@$xcW5Hxe9^*@Q09Sk<@zrvLfC&bCts4 zQWf>EYPlbPUj@O11^kOSrhcV1ubz&o<`VzoYU75t`Brx{U7Rc4X9WEZ&eZk6oZIE1Nu={u9Qm#k#`Aa)rHCs409Q?-aZ+ z{pp{&`D_s05^F&}c3iSu;6#7)O^rKI<^^RitDwgdd@y_x&!EV|j;z8&W%e<~6O+nO zEC%bR89-l>*G0${n9g;WT^dhZF9Y&Q2HVZ7_$VZxAbJ8@9X4gbsO{~O<}*ZbFIhdwR*NjRYvw!72oY3 zTz#aPt@9^?dAx`%$fU$E&0FiJcPFAcTc^ONekVv+)4-A-kRn?!Z{#UYC} zN(-`l+?RHN z?*g3}8RdP4>$-4tGbQ-MK6#^FLkpQJuaAyWn7|c8y+AfXN(^L$m)zvwSe=v)Ov*N! zG9@HH@59#x>^kL1l%Dp{wcZD23NbB~tMp7!ty_9c4Lc_kH-8UgFX&4`5NCWvTzX*R znw-DYwQkrteU3LJMt-Zy0iVmX6AW7A*d3j^Y3#-G5xfZdQ1V7?i=Ppfa~1o?ln?!5 zqAR9lwvV?e*D^PYe?R-ofl4~&jEz^_T9SnD#(49|uJ;rE6+*Yk?zwjXfVolG*ZP9* zHBXCyvhi%VxUA5G^P?Ygj&{zg#4l{lvv^l5X3SZ{k; zA8A6?Ik5}4#fp2t<5cXrn5DHCp2TovX3eEhMhK&U;Dza)T>v7^kJ9{>DR8hy302G@ zw}5Ftb|t}DTir3h|Ez2v6U4H48}GV9*acQU^+Odt8n^~Wn)37*B}8DKo_b?mm|ZX_ zmiqL~RsDM{h1?)-Wk=<7vE;F(F5^&24DhF)UN+u+A|0mncJV>3m0k8!b|F3ik=XCQ z<6+VVYg`>*-?k6^5VrHdT1K)?`Q)5}$EnP``p}4SnIL7Ad4JAbAXjCHPn^+6{Xfh| zCxj#MH8>aQB2aPt37DFT5EBz)LV8*>tA8|A8Ko<2*0ckr@Yq}^3UB6AO za>0R>W-vPdVv;40w2>Cqk9yG2q%6D(bhp$8x{({%p96r%;>0Yu);Q0|(4_g`kX?7dp|WMOkEQydeQ0QC3XAmd;4ML>oeyFXQO5bNt)7fL44&$a zd$rLT`sQubh=WmmBnWeQ%kU%0V3<5kL$SZV4%WY!7z^Cs=%cRjip^eU;9}iaoNzxZ z(&D5^B2x;cEE;nkirXrh5qNU+B9cWCQ0OH8XPDHER*kVNjD`Zz$^b*-zQzLDE!cV>dePRequi%9vB9Pe=hb)5If! zU#L8>s&$qespiyhS$PaOlYUm_FNa63K7l4}dXwaIrl7Ft_DHMWyzAl;(iN*KLln-= zK~FNyalR@J{>$=Wk&N@xp23Ll>CZ{GF;x~852g*I5=VR9$8hOshlW<=@}#HX&((9M7{VE{b4k?Gt)DYY2e^*BGFB z zRo0^!c2#^niB1(~O47E8AI77j{fhg70a&bJ6&HY-K=c3A>S`FwqCppvNb$w-8vLgig&i& z%1o>bn0NwnC@eQwfnsw=e5O;&-*X2J*nR=VEVl=?zC|xRxM!Z~WzO<4n?g0hGZP;D zxA8W3f4q&yGVw)-#y+gp!aayn=S;C6P1;un*z66r?kUDOlBojpk-RH8j{)Em9$MaW z>GUC6Hi7fLCL8YDcd&URk#^l?bXaIcY@_T5&DjYqR?p7^V5!9FH|yYOm0ch$Y05&q zWk&YXwRR^W{Nv`5qWu7(T})m`<{XUG$c62m?s;65xKMUw2WmAte#{!psZB}=t2Y<2 zyYwyjgVBbo@W_L5o-;3MIr`q_BjtYUw=4p3-5V&r1@6nD@(;o=7?Z>H^w_ZyR4eOcjm`WC-p(;q%jJVX;v%r{LAEl-r=C|W9INaY=mJ}NQTZ`oNZZ4 zthh~URIek!+`6`FqlslmVhz3v$ZzV~z}bGg^O&j`1_O(MS*>jau0Id%C1{nR9L>V= zNcj*p#WUn~HMF?4A7qS?GdiIrcAEDpHei*2vM$ZN`??X;Yw4~k+Q3^WXcwd*fMN7Q zgrA&upPB6actQP@4P(DnDp?(F;F!ddjIHg+nCJ4>cqYXcjlnt?N@wn{Ai6qLQy#Tt zyc(0N5SSC=sg9N*+DF~8NV56-RE=P-b@wC4*J+D)5oT**fKcQvf0l#t&=7C=trq3G zebbJJXNso9$xHJ!2O3x6Dz&RqJTA*!EV^d$69>XrbM6yP<8HogfxWjImm&vjUmP)Vu`Df-7O54kn zf!oHUQ-9@jB@%3k9UK&nv?DUi?>C7H-2D{Btd-=>lHR2j3Vk{q2hvP3+yqr1OY~n5 zqQ!>`N~Dsi^gK*G&`jwy#^G7s_KTvil2z=evd@;pXno?H-V1!T=G{iDTc?WpOVX`o z_*^xcWlFG>FYh?UHiO31)u~O~C8vvrYL~`G;VoQQeX$ne&5}kz0_7*2D*_4Vx|Kd3 zmC0*7xx)6CgL9Y5l1g$+*HCb_EX_$?a1q*C>!h}spmHlKOquddM<4D-8GAAkB0DVF zCoY-*_KY9&11*4jJ6+8RB$EvMfcHhk=wbjEv^mCdfBxHK8C0E z;sHPSWAn)^Zyo{Y7m);^vvLK6F)@TQF`{!qu3x0Vm=SxS`%!z6grykTk% zA4pJJK=Cm#%cpo7K$kEdWfd506;5fCq?sp*`o&KO9cd&tL2h0Vd-FIx_01eys8@t* zvDiWyyInY^oBwLOcRBCeiu^-=Z1OyTc|mH=`+?dnu1(;CEvwp2YF+hCAue4XU5O}u zk=Xvl-u(3H3QvmdtwbAToS)@o-aD!x!i-MEl)saPn_J7?Y7o_&XplgoFaNgmRp*Zi@ZoyX%?IenH=X8A;$YOM#zEWo&KWVPsZ|yQ&$GZ{c>>4_ym$et-)IIy|{&FZ4NJ= zb1a*u&`~fi-pMo5F$r{^2m1PnT?Oa0*4pgHbM@xNg(oHJKzc$0y5;wk3y2Ji9GzH2 z?Q3&KnQ_HpB+t(w=Cgw$QE`!#?{hDu6m&Ibrh595+uokl#8&G__pmn|zhz;7`Y2g8 z@Mb@@gU+=3puZgsas~bd`z{`cmES>s|NeI^+PKc6aG8+xMW!7JO98_V06x)8rPLJ6;F5LVE?(gj(zv>$~9Bsd?+fpr)}^T=Q}FiF4JAL#FL#7MVR2%Pi+?B0ukzvDf&T*e CNgw?H literal 0 HcmV?d00001 diff --git a/UnitystationLauncher/Models/AuthenticationStuff.cs b/UnitystationLauncher/Models/AuthenticationStuff.cs new file mode 100644 index 00000000..614b87a8 --- /dev/null +++ b/UnitystationLauncher/Models/AuthenticationStuff.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Text.Json.Nodes; +using Newtonsoft.Json; + +namespace UnitystationLauncher.Models; + +public abstract class JsonObject +{ + public virtual string ToJson() + { + return JsonConvert.SerializeObject(this); + } + + public virtual StringContent ToStringContent() + { + return new StringContent(ToJson(), Encoding.UTF8, "application/json"); + } +} + +[Serializable] +public class AccountLoginResponse : JsonObject +{ + [JsonProperty("token")] + public string Token {get; set;} + + [JsonProperty("account")] + public AccountGetResponse Account {get; set;} +} + +[Serializable] +public class AccountGetResponse : JsonObject +{ + [JsonProperty("unique_identifier")] + public string UniqueIdentifier {get; set;} + + [JsonProperty("username")] + public string Username {get; set;} + + [JsonProperty("is_verified")] + public bool IsVerified {get; set;} +} + +[Serializable] +public class AccountLoginCredentials : JsonObject +{ + [JsonProperty("email")] + public string Email {get; set;} + + [JsonProperty("password")] + public string Password {get; set;} +} + +public class ApiResult: JsonObject where T : JsonObject +{ + public HttpStatusCode StatusCode { get; set; } + public T Data { get; set; } + public ApiHttpException Exception { get; set; } + + public bool IsSuccess => Exception == null; + + private ApiResult(HttpStatusCode statusCode, T data, ApiHttpException exception = null) + { + StatusCode = statusCode; + Data = data; + Exception = exception; + } + + public static ApiResult Success(HttpStatusCode statusCode, T data) => new(statusCode, data); + public static ApiResult Failure(HttpStatusCode statusCode, T data, ApiHttpException exception) => new(statusCode, data, exception); +} + +/// +/// Error class for any HTTP-related errors as returned by the API server. +/// +public class ApiHttpException : Exception +{ + public HttpStatusCode StatusCode { get; private set; } + + public ApiHttpException(string message, HttpStatusCode code) : base(message) + { + StatusCode = code; + } +} + +/// +/// Marks an API request as having or requiring an authentication token. +/// +public interface ITokenAuthable +{ + string Token { get; } +} + +/// +/// Error class for any usage-specific API errors as returned by the API server. +/// +public class ApiRequestException : ApiHttpException +{ + /// A list of all error messages returned by the API server. + /// You can use Message to get the first one. + public List Messages { get; set; } + + public ApiRequestException(string message, HttpStatusCode statusCode) : base(message, statusCode) + { + Messages = new List(); + } +} + diff --git a/UnitystationLauncher/Models/User.cs b/UnitystationLauncher/Models/User.cs new file mode 100644 index 00000000..9cc34a53 --- /dev/null +++ b/UnitystationLauncher/Models/User.cs @@ -0,0 +1,7 @@ +namespace UnitystationLauncher.Models; + +public class User +{ + public string Email; + public string LocalId; +} \ No newline at end of file diff --git a/UnitystationLauncher/Services/ApiServer.cs b/UnitystationLauncher/Services/ApiServer.cs new file mode 100644 index 00000000..d8c36833 --- /dev/null +++ b/UnitystationLauncher/Services/ApiServer.cs @@ -0,0 +1,158 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using UnitystationLauncher.Models; + +namespace UnitystationLauncher.Services; + +/// +/// HTTP wrapper for database API requests. +/// +public static class ApiServer +{ + internal static async Task> Get(Uri uri, string token = default) where T : JsonObject + { + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri); + + if (token != default) + { + request.Headers.Authorization = new AuthenticationHeaderValue("Token", token); + } + + return await Send(request); + } + + internal static async Task> Post(Uri uri, JsonObject body, string token = default) + where T : JsonObject + { + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri); + + if (body is ITokenAuthable authable) + { + request.Headers.Authorization = new AuthenticationHeaderValue("Token", authable.Token); + } + + if (token != default) + { + request.Headers.Authorization = new AuthenticationHeaderValue("Token", token); + } + + string sss = JsonConvert.SerializeObject(body); + request.Content = new StringContent(sss, Encoding.UTF8, "application/json"); + return await Send(request); + } + + internal static async Task> Put(Uri uri, JsonObject body, string token = default) + where T : JsonObject + { + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Put, uri); + + if (body is ITokenAuthable authable) + { + request.Headers.Authorization = new AuthenticationHeaderValue("Token", authable.Token); + } + + if (token != default) + { + request.Headers.Authorization = new AuthenticationHeaderValue("Token", token); + } + + var sss = JsonConvert.SerializeObject(body); + request.Content = new StringContent(sss, Encoding.UTF8, "application/json"); + //request.Content = body.ToStringContent(); + return await Send(request); + } + + internal static async Task> Delete(Uri uri, string token = default) where T : JsonObject + { + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Delete, uri); + + if (token != default) + { + request.Headers.Authorization = new AuthenticationHeaderValue("Token", token); + } + + return await Send(request); + } + + + private static async Task> Send(HttpRequestMessage request) where T : JsonObject + { + request.Headers.Add("Accept", "application/json"); + HttpClient Client = new HttpClient(); + HttpResponseMessage response = await Client.SendAsync(request); + string responseBody = await response.Content.ReadAsStringAsync(); + + if (response.IsSuccessStatusCode == false) + { + if (TryGetApiRequestException(responseBody, response.StatusCode, out ApiRequestException requestException)) + { + return ApiResult.Failure(response.StatusCode, null, requestException); + } + + return ApiResult.Failure(response.StatusCode, null, + new ApiHttpException(response.ReasonPhrase, response.StatusCode)); + } + + return ApiResult.Success(response.StatusCode, JsonConvert.DeserializeObject(responseBody)); + } + + /// + /// Attempts to get any usage-related errors from the given API server's response and + /// provides an unthrown instance if any are found. + /// + /// Response body to test + /// + /// An or null + /// True if an API error found + private static bool TryGetApiRequestException(string response, HttpStatusCode statusCode, + out ApiRequestException requestException) + { + ApiErrorResponse errorResponse = JsonConvert.DeserializeObject(response); + ApiRequestException tempException = new ApiRequestException("An error occurred", statusCode); + requestException = null; + + if (errorResponse.Error is JValue) + { + HandleSimpleError(tempException, errorResponse); + requestException = tempException; + } + else if (errorResponse.Error is JObject) + { + HandleComplexError(tempException, errorResponse); + requestException = tempException; + } + + return requestException != null && requestException.Messages.Any(); + } + + public class ApiErrorResponse + { + [JsonProperty("error")] public JToken Error { get; set; } + } + + private static void HandleComplexError(ApiRequestException requestException, ApiErrorResponse errorResponse) + { + if (errorResponse.Error is JObject errorObject) + { + foreach (var prop in errorObject.Properties()) + { + foreach (var message in prop.Value) + { + requestException.Messages.Add(message.ToString()); + } + } + } + } + + private static void HandleSimpleError(ApiRequestException requestException, ApiErrorResponse errorResponse) + { + requestException.Messages.Add(errorResponse.Error.ToString()); + } +} \ No newline at end of file diff --git a/UnitystationLauncher/Services/AuthService.cs b/UnitystationLauncher/Services/AuthService.cs new file mode 100644 index 00000000..71791200 --- /dev/null +++ b/UnitystationLauncher/Services/AuthService.cs @@ -0,0 +1,260 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Net.Mail; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; +using Serilog; +using UnitystationLauncher.Constants; +using UnitystationLauncher.Models; +using UnitystationLauncher.Models.ConfigFile; + +namespace UnitystationLauncher.Services +{ + public class AuthService + { + private readonly HttpClient _http; + public LoginMsg? LoginMsg { get; set; } + public bool AttemptingAutoLogin { get; set; } + public IAuthProvider _IAuthProvider; + + public AuthService(HttpClient http, IAuthProvider IAuthProvider) + { + _http = http; + _IAuthProvider = IAuthProvider; + LoadAuthSettings(); + } + + + public string? CurrentRefreshToken => "AccountLoginResponse?.RefreshToken"; + + public string? Uid => "AccountLoginResponse?.User.LocalId"; + + private readonly string AuthSettingsPath = Path.Combine("TODO", "authSettings.json"); + + public AccountLoginResponse AccountLoginResponse; + + private void ConvertToNewAuthFileName() + { + string oldAuthSettingsPath = Path.Combine("TODO", "settings.json"); + if (File.Exists(oldAuthSettingsPath)) + { + File.Move(oldAuthSettingsPath, AuthSettingsPath); + } + } + + private void LoadAuthSettings() + { + try + { + ConvertToNewAuthFileName(); + + if (File.Exists(AuthSettingsPath)) + { + var json = File.ReadAllText(AuthSettingsPath); + // var AccountLoginResponse = JsonSerializer.Deserialize(json); + // AccountLoginResponse = AccountLoginResponse; + } + } + catch (Exception) + { + // Something went wrong reading the auth settings. Just ask the user to log in again. + // The auth settings file will get overwritten after they do so we don't need to clean it up. + } + + } + + public void SaveAuthSettings() + { + var json = JsonSerializer.Serialize(AccountLoginResponse); + + using (StreamWriter writer = File.CreateText(AuthSettingsPath)) + { + writer.WriteLine(json); + } + } + + public void ResendVerificationEmail() + { + //_authProvider.SendEmailVerificationAsync(AccountLoginResponse); + } + + public void SendForgotPasswordEmail(string email) + { + //_authProvider.SendPasswordResetEmailAsync(email); + } + + internal Task SignInWithEmailAndPasswordAsync(string email, string password) + { + _IAuthProvider.SignInWithEmailAndPasswordAsync(email, password); + return Task.FromResult(new AccountLoginResponse());; + } + + + internal Task SignInWithCustomTokenAsync(string token) + { + return Task.FromResult(new AccountLoginResponse());; + } + + + /// + /// Asks firebase to create the user's account. + /// The provided email's domain is checked against a list of disposable email addresses. + /// If the domain is not in the list (or if GitHub is down) then account creation continues. + /// Otherwise an exception is thrown. + /// + /// + internal async Task CreateAccountAsync(string username, string email, string password) + { + // Client-side check for disposable email address. + const string url = + "https://raw.githubusercontent.com/martenson/disposable-email-domains/master/disposable_email_blocklist.conf"; + HttpRequestMessage requestMessage = new(HttpMethod.Get, url); + + CancellationToken cancellationToken = new CancellationTokenSource(60000).Token; + bool isDomainBlacklisted = false; + try + { + HttpResponseMessage response = await _http.SendAsync(requestMessage, cancellationToken); + string msg = await response.Content.ReadAsStringAsync(cancellationToken); + + // Turn msg into a hashset of all domains + using StringReader stringReader = new(msg); + List lines = new(); + + while (await stringReader.ReadLineAsync() is { } line) + { + if (!string.IsNullOrWhiteSpace(line) && !line.TrimStart().StartsWith("//")) + { + lines.Add(line); + } + } + + HashSet blacklist = new(lines, StringComparer.OrdinalIgnoreCase); + + MailAddress address = new(email); + if (blacklist.Contains(address.Host)) + { + // Randomly wait before failing. Might frustrate users who try different disposable emails. + await Task.Delay(new Random().Next(3000, 12000), cancellationToken); + isDomainBlacklisted = true; + } + } + catch (Exception e) + { + Log.Error(e, "Error or timeout in check for email domain blacklist, check has been skipped"); + } + + if (isDomainBlacklisted) + { + throw new InvalidOperationException("The email domain provided by the user is on our blacklist."); + } + + //return await _authProvider.CreateUserWithEmailAndPasswordAsync(email, password, username, true); + return AccountLoginResponse; + } + + internal Task GetUpdatedUserAsync() + { + return null; + //return _authProvider.GetUserAsync(AccountLoginResponse); + } + + public async Task GetCustomTokenAsync(RefreshToken refreshToken) + { + HttpRequestMessage r = new(HttpMethod.Get, ApiUrls.ValidateTokenUrl + Uri.EscapeDataString(JsonSerializer.Serialize(refreshToken))); + CancellationToken cancellationToken = new CancellationTokenSource(120000).Token; + HttpResponseMessage res; + + try + { + res = await _http.SendAsync(r, cancellationToken); + } + catch (Exception e) + { + Log.Error(e, "Failed when sending token validation request"); + return ""; + } + + string msg = await res.Content.ReadAsStringAsync(cancellationToken); + ApiResponse? response = JsonSerializer.Deserialize(msg, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + if (response == null) + { + Log.Error("Error: {Error}", "Response from /validatetoken cannot be deserialized"); + return ""; + } + + if (response.ErrorCode != 0) + { + Log.Error("Error: {Error}", response.ErrorMsg); + return ""; + } + + return response.Message ?? ""; + } + + public async Task SignOutUserAsync() + { + if (AccountLoginResponse == null || Uid == null || CurrentRefreshToken == null) + { + return; + } + + RefreshToken token = new() + { + UserId = Uid, + Token = CurrentRefreshToken + }; + + HttpRequestMessage r = new(HttpMethod.Get, ApiUrls.SignOutUrl + Uri.EscapeDataString(JsonSerializer.Serialize(token))); + CancellationToken cancellationToken = new CancellationTokenSource(120000).Token; + HttpResponseMessage res; + + try + { + res = await _http.SendAsync(r, cancellationToken); + } + catch (Exception e) + { + Log.Error(e, "Http request to sign out failed"); + return; + } + + string msg = await res.Content.ReadAsStringAsync(cancellationToken); + + Log.Information("Logout message: {Message}", msg); + AccountLoginResponse = null; + } + } + + + + public class LoginMsg + { + public string Email { get; set; } = ""; + public string Pass { get; set; } = ""; + } + + [Serializable] + public class RefreshToken + { + [JsonPropertyName("RefreshToken")] public string? Token { get; set; } + public string? UserId { get; set; } + } + + [Serializable] + public class ApiResponse + { + /// + /// 0 = all good, read the message variable now, otherwise read errorMsg + /// + public int ErrorCode { get; set; } + + public string? ErrorMsg { get; set; } + public string? Message { get; set; } + } +} \ No newline at end of file diff --git a/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs b/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs new file mode 100644 index 00000000..c3b27c33 --- /dev/null +++ b/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs @@ -0,0 +1,56 @@ +using System; +using System.Threading.Tasks; +using UnitystationLauncher.Models; + +namespace UnitystationLauncher.Services; + +public interface IAuthProvider +{ + public Task SignInWithEmailAndPasswordAsync(string email, string password); +} + +public class OfficialCentralCommandAuthentication : IAuthProvider +{ + + public static string Host => "INhere"; + public static UriBuilder UriBuilder = new("https", Host); + public static Uri GetUri(string endpoint, string queries = null) + { + + UriBuilder.Path = $"/accounts/{endpoint}"; + + if (string.IsNullOrEmpty(queries) == false) + { + UriBuilder.Query = queries; + } + + return UriBuilder.Uri; + } + + public static async Task> Login(string emailAddress, string password) + { + AccountLoginCredentials requestBody = new() + { + Email = emailAddress, + Password = password, + }; + + ApiResult response = await ApiServer.Post(GetUri("login-credentials"), requestBody); + + if (!response.IsSuccess) + { + throw response.Exception!; + } + + return response; + } + + public async Task SignInWithEmailAndPasswordAsync(string emailAddress, string password) + { + ApiResult loginResponse = await Login(emailAddress, password); + + AccountLoginResponse account = loginResponse.Data; + + return account; + } +} \ No newline at end of file diff --git a/UnitystationLauncher/StandardModule.cs b/UnitystationLauncher/StandardModule.cs index a4bfb11a..9029474e 100644 --- a/UnitystationLauncher/StandardModule.cs +++ b/UnitystationLauncher/StandardModule.cs @@ -24,7 +24,8 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); - + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); // View Models builder.RegisterAssemblyTypes(ThisAssembly) .Where(t => t.Name.EndsWith("ViewModel")); diff --git a/UnitystationLauncher/ViewModels/ForgotPasswordViewModel.cs b/UnitystationLauncher/ViewModels/ForgotPasswordViewModel.cs new file mode 100644 index 00000000..fe1b6983 --- /dev/null +++ b/UnitystationLauncher/ViewModels/ForgotPasswordViewModel.cs @@ -0,0 +1,72 @@ +using System; +using System.Reactive; +using ReactiveUI; +using UnitystationLauncher.Services; + +namespace UnitystationLauncher.ViewModels +{ + public class ForgotPasswordViewModel : ViewModelBase + { + private readonly Lazy _loginVm; + private readonly AuthService _authService; + private bool _isFormVisible; + private bool _isSuccessVisible; + string _email = ""; + + public string Email + { + get => _email; + set => this.RaiseAndSetIfChanged(ref _email, value); + } + + public ReactiveCommand Submit { get; } + public ReactiveCommand DoneButton { get; } + + public bool IsFormVisible + { + get => _isFormVisible; + set => this.RaiseAndSetIfChanged(ref _isFormVisible, value); + } + + public bool IsSuccessVisible + { + get => _isSuccessVisible; + set => this.RaiseAndSetIfChanged(ref _isSuccessVisible, value); + } + + public ForgotPasswordViewModel(AuthService authService, Lazy loginVm) + { + IsFormVisible = true; + IsSuccessVisible = false; + _authService = authService; + _loginVm = loginVm; + + var inputValidation = this.WhenAnyValue( + x => x.Email, + (e) => !string.IsNullOrWhiteSpace(e) && + e.Contains("@") && e.Contains(".")); + + Submit = ReactiveCommand.Create( + TrySendResetPassword, inputValidation); + + DoneButton = ReactiveCommand.Create(ReturnToLogin); + } + + void TrySendResetPassword() + { + _authService.SendForgotPasswordEmail(Email); + IsFormVisible = false; + IsSuccessVisible = true; + } + + public override void Refresh() + { + + } + + public LoginViewModel ReturnToLogin() + { + return _loginVm.Value; + } + } +} diff --git a/UnitystationLauncher/ViewModels/LoginStatusViewModel.cs b/UnitystationLauncher/ViewModels/LoginStatusViewModel.cs new file mode 100644 index 00000000..30dda627 --- /dev/null +++ b/UnitystationLauncher/ViewModels/LoginStatusViewModel.cs @@ -0,0 +1,180 @@ +using System; +using System.Reactive; +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using System.Threading.Tasks; +using ReactiveUI; +using Serilog; +using UnitystationLauncher.Infrastructure; +using UnitystationLauncher.Services; + +namespace UnitystationLauncher.ViewModels +{ + public class LoginStatusViewModel : ViewModelBase + { + private readonly AuthService _authService; + private readonly Lazy _launcherVm; + private readonly LoginViewModel _loginVm; + private string? _failedMessage; + private bool _isFailedVisible; + private bool _isResendEmailVisible; + private bool _resendClicked; + private bool _isWaitingVisible; + + public LoginStatusViewModel(AuthService authService, Lazy launcherVm, + LoginViewModel loginVm) + { + IsFailedVisible = false; + IsResendEmailVisible = false; + ResendClicked = false; + _authService = authService; + _loginVm = loginVm; + _launcherVm = launcherVm; + + var hasAlreadyResent = this.WhenAnyValue( + x => x.ResendClicked, + (r) => !r); + + ResendEmail = ReactiveCommand.Create(OnResend, hasAlreadyResent); + + GoBack = ReactiveCommand.Create(GoBackToLogin); + + OpenLauncher = ReactiveCommand.Create(SignInComplete); + + if (!authService.AttemptingAutoLogin) + { + RxApp.MainThreadScheduler.ScheduleAsync((scheduler, ct) => UserLoginAsync()); + } + else + { + IsWaitingVisible = true; + } + } + + public bool IsFailedVisible + { + get => _isFailedVisible; + set => this.RaiseAndSetIfChanged(ref _isFailedVisible, value); + } + + public bool IsResendEmailVisible + { + get => _isResendEmailVisible; + set => this.RaiseAndSetIfChanged(ref _isResendEmailVisible, value); + } + + public string? FailedMessage + { + get => _failedMessage; + set => this.RaiseAndSetIfChanged(ref _failedMessage, value); + } + + public bool ResendClicked + { + get => _resendClicked; + set => this.RaiseAndSetIfChanged(ref _resendClicked, value); + } + + public bool IsWaitingVisible + { + get => _isWaitingVisible; + set => this.RaiseAndSetIfChanged(ref _isWaitingVisible, value); + } + + public ReactiveCommand GoBack { get; } + public ReactiveCommand ResendEmail { get; } + public ReactiveCommand OpenLauncher { get; } + + public async Task UserLoginAsync() + { + bool signInSuccess = true; + ResendClicked = false; + IsResendEmailVisible = false; + IsWaitingVisible = true; + + if (string.IsNullOrEmpty(_authService.LoginMsg?.Email) || + string.IsNullOrEmpty(_authService.LoginMsg.Pass)) + { + Log.Error("Login failed"); + FailedMessage = "Login failed.\r\n" + + "Check your email and password\r\n" + + "and try again."; + return; + } + + try + { + await _authService.SignInWithEmailAndPasswordAsync( + _authService.LoginMsg.Email, _authService.LoginMsg.Pass).AwaitWithTimeout(TimeSpan.FromSeconds(20), + firebaseAccountLoginResponse => _authService.AccountLoginResponse = firebaseAccountLoginResponse); + } + catch (OperationCanceledException) + { + Log.Error("Error: {Error}", "Login timed out"); + FailedMessage = "Timed out while trying to log in.\n" + + "Please check your network connection."; + signInSuccess = false; + } + catch (Exception e) + { + Log.Error(e, "Login failed"); + FailedMessage = "Login failed.\r\n" + + "Check your email and password\r\n" + + "and try again."; + signInSuccess = false; + } + + if (signInSuccess) + { + var user = await _authService.GetUpdatedUserAsync(); + + // if (!user.IsEmailVerified) + // { + // FailedMessage = "Email not yet verified.\r\n" + + // "Please click on the activation link sent to your\r\n" + + // "email address. Alternatively you can request another verification\r\n" + + // "email by clicking the resend button below."; + // signInSuccess = false; + // IsResendEmailVisible = true; + // } + } + + _authService.LoginMsg = null; + + IsWaitingVisible = false; + if (!signInSuccess) + { + IsFailedVisible = true; + return; + } + + _authService.SaveAuthSettings(); + + Observable.Start(() => { }).InvokeCommand(this, vm => vm.OpenLauncher); + } + + public void OnResend() + { + _authService.ResendVerificationEmail(); + ResendClicked = true; + FailedMessage = "A new verification email has been sent to:\r\n" + + $"{_authService.AccountLoginResponse?.Account.Username ?? "{ no email }"}\r\n" + + $"Please activate your account by clicking the link\r\n" + + $"in the email and try again."; + } + + public LoginViewModel GoBackToLogin() + { + return _loginVm; + } + public LauncherViewModel SignInComplete() + { + return _launcherVm.Value; + } + + public override void Refresh() + { + + } + } +} \ No newline at end of file diff --git a/UnitystationLauncher/ViewModels/LoginViewModel.cs b/UnitystationLauncher/ViewModels/LoginViewModel.cs new file mode 100644 index 00000000..f6a49cc0 --- /dev/null +++ b/UnitystationLauncher/ViewModels/LoginViewModel.cs @@ -0,0 +1,106 @@ +using System; +using System.Reactive; +using System.Reactive.Concurrency; +using System.Threading.Tasks; +using ReactiveUI; +using UnitystationLauncher.Models.ConfigFile; +using UnitystationLauncher.Services; + +namespace UnitystationLauncher.ViewModels +{ + public class LoginViewModel : ViewModelBase + { + private readonly Lazy _signUpVm; + private readonly Lazy _forgotVm; + private readonly Lazy _loginStatusVm; + private readonly AuthService _authService; + string _email = ""; + string _password = ""; + + public LoginViewModel( + Lazy loginStatusVm, + Lazy signUpVm, + Lazy forgotVm, + AuthService authService) + { + _authService = authService; + _signUpVm = signUpVm; + _loginStatusVm = loginStatusVm; + _forgotVm = forgotVm; + + var possibleCredentials = this.WhenAnyValue( + x => x.Email, + x => x.Password, + (u, p) => + !string.IsNullOrWhiteSpace(u) && + !string.IsNullOrWhiteSpace(p)); + + Login = ReactiveCommand.CreateFromTask( + UserLoginAsync, + possibleCredentials); + + Create = ReactiveCommand.Create( + UserCreate); + + ForgotPw = ReactiveCommand.Create( + ForgotPass); + + RxApp.MainThreadScheduler.ScheduleAsync((scheduler, ct) => CheckForLastLoginAsync()); + } + + public string Email + { + get => _email; + set => this.RaiseAndSetIfChanged(ref _email, value); + } + + public string Password + { + get => _password; + set => this.RaiseAndSetIfChanged(ref _password, value); + } + + public ReactiveCommand Login { get; } + public ReactiveCommand Create { get; } + public ReactiveCommand ForgotPw { get; } + + public async Task UserLoginAsync() + { + _authService.LoginMsg = new LoginMsg + { + Email = Email, + Pass = Password + }; + + await SaveLoginEmailAsync(); + + return _loginStatusVm.Value; + } + + public SignUpViewModel UserCreate() + { + return _signUpVm.Value; + } + + public ForgotPasswordViewModel ForgotPass() + { + return _forgotVm.Value; + } + + public override void Refresh() + { + + } + + async Task CheckForLastLoginAsync() + { + //Email = (await _config.GetPreferencesAsync()).LastLogin ?? ""; + } + + async Task SaveLoginEmailAsync() + { + // var prefs = await _config.GetPreferencesAsync(); + // prefs.LastLogin = _email; + } + } +} diff --git a/UnitystationLauncher/ViewModels/MainWindowViewModel.cs b/UnitystationLauncher/ViewModels/MainWindowViewModel.cs index 8985603c..07ee341a 100644 --- a/UnitystationLauncher/ViewModels/MainWindowViewModel.cs +++ b/UnitystationLauncher/ViewModels/MainWindowViewModel.cs @@ -1,14 +1,23 @@ using System; +using System.Reactive.Concurrency; using ReactiveUI; using System.Reactive.Linq; using System.Threading; +using System.Threading.Tasks; using Avalonia.Media; +using Serilog; +using UnitystationLauncher.Services; namespace UnitystationLauncher.ViewModels { public class MainWindowViewModel : ViewModelBase { - private ViewModelBase _content; + private readonly Lazy _launcherVm; + private readonly Lazy _loginStatusVm; + private readonly AuthService _authService; + private readonly LoginViewModel _loginVm; + + ViewModelBase _content; private Geometry _maximizeIcon; private string _maximizeToolTip; @@ -24,11 +33,18 @@ public string MaximizeToolTip set => this.RaiseAndSetIfChanged(ref _maximizeToolTip, value); } - public MainWindowViewModel(LauncherViewModel launcherVm) + public MainWindowViewModel(LoginViewModel loginVm, Lazy loginStatusVm, Lazy launcherVm, + AuthService authService) { + _loginStatusVm = loginStatusVm; + _loginVm = loginVm; + _authService = authService; + _launcherVm = launcherVm; + Content = _content = loginVm; + authService.AttemptingAutoLogin = false; _maximizeIcon = Geometry.Parse("M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z"); _maximizeToolTip = "Maximize"; - Content = _content = launcherVm; + RxApp.MainThreadScheduler.ScheduleAsync((_, _) => CheckForExistingUserAsync()); } private void Maximize() @@ -57,17 +73,95 @@ private set } } + async Task CheckForExistingUserAsync() + { + if (_authService.AccountLoginResponse != null) + { + _authService.AttemptingAutoLogin = true; + Content = _loginStatusVm.Value; + await AttemptAuthRefreshAsync(); + } + } + + async Task AttemptAuthRefreshAsync() + { + if (_authService.AccountLoginResponse == null) + { + Log.Error("Login failed"); + Content = _loginVm; + _authService.AttemptingAutoLogin = false; + return; + } + + RefreshToken refreshToken = new() + { + // UserId = _authService.AccountLoginResponse.User.LocalId, + // Token = _authService.AccountLoginResponse.RefreshToken + }; + + string token = await _authService.GetCustomTokenAsync(refreshToken); + + if (string.IsNullOrEmpty(token)) + { + Log.Error("Login failed"); + Content = _loginVm; + _authService.AttemptingAutoLogin = false; + return; + } + + try + { + _authService.AccountLoginResponse = await _authService.SignInWithCustomTokenAsync(token); + } + catch (Exception e) + { + Log.Error(e, "Login failed"); + Content = _loginVm; + _authService.AttemptingAutoLogin = false; + return; + } + + var user = await _authService.GetUpdatedUserAsync(); + // if (!user.IsEmailVerified) + // { + // Content = _loginVm; + // _authService.AttemptingAutoLogin = false; + // return; + // } + _authService.AttemptingAutoLogin = false; + _authService.SaveAuthSettings(); + Content = _launcherVm.Value; + } + private void ContentChanged() { SubscribeToVm(Content switch { - LauncherViewModel launcherVm => Observable.Merge( - launcherVm.ShowUpdateView.Select(vm => (ViewModelBase)vm)), + LoginViewModel loginVm => Observable.Merge( + loginVm.Login.Select(vm => (ViewModelBase)vm), + loginVm.Create.Select(vm => (ViewModelBase)vm), + loginVm.ForgotPw.Select(vm => (ViewModelBase)vm)), + + LoginStatusViewModel loginStatusVm => Observable.Merge( + loginStatusVm.GoBack.Select(vm => (ViewModelBase)vm), + loginStatusVm.OpenLauncher.Select(vm => (ViewModelBase)vm)), + + // LauncherViewModel launcherVm => Observable.Merge( + // launcherVm.Logout.Select(vm => (ViewModelBase)vm), + // launcherVm.ShowUpdateView.Select(vm => (ViewModelBase)vm)), + + SignUpViewModel signUpViewModel => Observable.Merge( + signUpViewModel.Cancel, + signUpViewModel.DoneButton), HubUpdateViewModel hubUpdateViewModel => Observable.Merge( hubUpdateViewModel.Skip, hubUpdateViewModel.Ignore), + ForgotPasswordViewModel forgotPasswordViewModel => Observable.Merge( + forgotPasswordViewModel.DoneButton), + + _ => throw new ArgumentException($"ViewModel type is not handled and will never be able to change") }); } diff --git a/UnitystationLauncher/ViewModels/SignUpViewModel.cs b/UnitystationLauncher/ViewModels/SignUpViewModel.cs new file mode 100644 index 00000000..1d9d52bb --- /dev/null +++ b/UnitystationLauncher/ViewModels/SignUpViewModel.cs @@ -0,0 +1,151 @@ +using System; +using System.Reactive; +using System.Threading.Tasks; +using ReactiveUI; +using Serilog; +using UnitystationLauncher.Services; + +namespace UnitystationLauncher.ViewModels +{ + public class SignUpViewModel : ViewModelBase + { + private readonly AuthService _authService; + private readonly Lazy _loginVm; + string _email = ""; + string _password = ""; + string _username = ""; + private string? _creationMessage; + private string? _endButtonText; + private bool _isFormVisible; + private bool _isWaitingVisible; + private bool _isCreatedVisible; + + public string Username + { + get => _username; + set => this.RaiseAndSetIfChanged(ref _username, value); + } + + public string Email + { + get => _email; + set => this.RaiseAndSetIfChanged(ref _email, value); + } + + public string Password + { + get => _password; + set => this.RaiseAndSetIfChanged(ref _password, value); + } + + public bool IsFormVisible + { + get => _isFormVisible; + set => this.RaiseAndSetIfChanged(ref _isFormVisible, value); + } + + public bool IsCreatedVisible + { + get => _isCreatedVisible; + set => this.RaiseAndSetIfChanged(ref _isCreatedVisible, value); + } + + public bool IsWaitingVisible + { + get => _isWaitingVisible; + set => this.RaiseAndSetIfChanged(ref _isWaitingVisible, value); + } + + public string? CreationMessage + { + get => _creationMessage; + set => this.RaiseAndSetIfChanged(ref _creationMessage, value); + } + + public string? EndButtonText + { + get => _endButtonText; + set => this.RaiseAndSetIfChanged(ref _endButtonText, value); + } + + public ReactiveCommand Cancel { get; } + public ReactiveCommand DoneButton { get; } + public ReactiveCommand Submit { get; } + + public SignUpViewModel(AuthService authService, Lazy loginVm) + { + IsFormVisible = true; + IsWaitingVisible = false; + IsCreatedVisible = false; + _authService = authService; + _loginVm = loginVm; + var possibleCredentials = this.WhenAnyValue( + x => x.Email, + x => x.Password, + x => x.Username, + (u, p, i) => + !string.IsNullOrWhiteSpace(u) && + !string.IsNullOrWhiteSpace(p) && + p.Length > 6 && + !string.IsNullOrEmpty(i)); + + Submit = ReactiveCommand.CreateFromTask( + UserCreateAsync, possibleCredentials); + + Cancel = ReactiveCommand.Create(ReturnToLogin); + + DoneButton = ReactiveCommand.Create(CreationEndButton); + } + + public override void Refresh() + { + + } + + public async Task UserCreateAsync() + { + IsFormVisible = false; + var creationSuccess = true; + IsWaitingVisible = true; + + try + { + await _authService.CreateAccountAsync(_username, _email, _password); + } + catch (Exception e) + { + Log.Error(e, "Login failed"); + creationSuccess = false; + } + + if (creationSuccess) + { + CreationMessage = $"Success! An email has been sent to \r\n{_email}\r\n" + + $"Please click the link in the email to verify\r\n" + + $"your account before signing in."; + EndButtonText = "Done"; + } + else + { + CreationMessage = $"Something went wrong with the verification email server.\r\n" + + $"A reset password email has been sent to {_email} as a work around.\r\n" + + $"Please reset your password and try to log in."; + _authService.SendForgotPasswordEmail(_email); + EndButtonText = "Back"; + } + + IsWaitingVisible = false; + IsCreatedVisible = true; + } + + public LoginViewModel ReturnToLogin() + { + return _loginVm.Value; + } + + public LoginViewModel CreationEndButton() + { + return _loginVm.Value; + } + } +} \ No newline at end of file diff --git a/UnitystationLauncher/Views/ForgotPasswordView.xaml b/UnitystationLauncher/Views/ForgotPasswordView.xaml new file mode 100644 index 00000000..85805346 --- /dev/null +++ b/UnitystationLauncher/Views/ForgotPasswordView.xaml @@ -0,0 +1,48 @@ + + + + + + + + + Enter your email address: + + + + + + + + + + + + + + + A reset password email has been sent to your address. + Please reset your password and try logging in again + + + + + + \ No newline at end of file diff --git a/UnitystationLauncher/Views/ForgotPasswordView.xaml.cs b/UnitystationLauncher/Views/ForgotPasswordView.xaml.cs new file mode 100644 index 00000000..1f080729 --- /dev/null +++ b/UnitystationLauncher/Views/ForgotPasswordView.xaml.cs @@ -0,0 +1,18 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace UnitystationLauncher.Views +{ + public class ForgotPasswordView : UserControl + { + public ForgotPasswordView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} \ No newline at end of file diff --git a/UnitystationLauncher/Views/LoginStatusView.xaml b/UnitystationLauncher/Views/LoginStatusView.xaml new file mode 100644 index 00000000..95ddb42d --- /dev/null +++ b/UnitystationLauncher/Views/LoginStatusView.xaml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UnitystationLauncher/Views/LoginView.xaml.cs b/UnitystationLauncher/Views/LoginView.xaml.cs new file mode 100644 index 00000000..7cb6e70c --- /dev/null +++ b/UnitystationLauncher/Views/LoginView.xaml.cs @@ -0,0 +1,18 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace UnitystationLauncher.Views +{ + public class LoginView : UserControl + { + public LoginView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} \ No newline at end of file diff --git a/UnitystationLauncher/Views/SignUpView.xaml b/UnitystationLauncher/Views/SignUpView.xaml new file mode 100644 index 00000000..0d551cc2 --- /dev/null +++ b/UnitystationLauncher/Views/SignUpView.xaml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + Create Account: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Bug Fixes:

      From 8ff9524fc6bd5c500f875c396fe21c6642df9b64 Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Sat, 19 Jul 2025 19:41:27 +0100 Subject: [PATCH 04/26] Format --- UnitystationLauncher/Constants/ApiUrls.cs | 6 ++-- .../PipeHubBuildCommunication.cs | 6 ++-- .../Models/AuthenticationStuff.cs | 34 +++++++++---------- UnitystationLauncher/Services/ApiServer.cs | 2 +- UnitystationLauncher/Services/AuthService.cs | 28 +++++++-------- .../OfficialCentralCommandAuthentication.cs | 30 ++++++++-------- .../ViewModels/ForgotPasswordViewModel.cs | 2 +- .../ViewModels/LoginStatusViewModel.cs | 10 +++--- .../ViewModels/LoginViewModel.cs | 6 ++-- .../ViewModels/MainWindowViewModel.cs | 8 ++--- .../ViewModels/SignUpViewModel.cs | 10 +++--- 11 files changed, 71 insertions(+), 71 deletions(-) diff --git a/UnitystationLauncher/Constants/ApiUrls.cs b/UnitystationLauncher/Constants/ApiUrls.cs index 52fefd7a..96826d09 100644 --- a/UnitystationLauncher/Constants/ApiUrls.cs +++ b/UnitystationLauncher/Constants/ApiUrls.cs @@ -20,8 +20,8 @@ public static class ApiUrls public static string CodeScanListUrl => $"{RawGitHubFileBaseUrl}/CodeScanList.json"; public static string TTSFiles => $"{CdnBaseUrl}/STTBundleTTS/TTS"; public static string TTSVersionFile => $"{TTSFiles}/version.txt"; - - + + public static string ApiBaseUrlLogin => "https://prod-api.unitystation.org"; - + } \ No newline at end of file diff --git a/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs b/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs index 80c20f7d..488dcfb8 100644 --- a/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs +++ b/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs @@ -15,17 +15,17 @@ public class PipeHubBuildCommunication : IDisposable { private static NamedPipeServerStream _serverPipe; private static StreamReader? _reader; - private static StreamWriter? _writer; + private static StreamWriter? _writer; public PipeHubBuildCommunication() { - _serverPipe?.Close(); + _serverPipe?.Close(); _serverPipe?.Dispose(); _reader?.Close(); _reader?.Dispose(); _writer?.Close(); _writer?.Dispose(); - + _serverPipe = new("Unitystation_Hub_Build_Communication", PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); } diff --git a/UnitystationLauncher/Models/AuthenticationStuff.cs b/UnitystationLauncher/Models/AuthenticationStuff.cs index dd3a8e4c..a91a29a8 100644 --- a/UnitystationLauncher/Models/AuthenticationStuff.cs +++ b/UnitystationLauncher/Models/AuthenticationStuff.cs @@ -29,13 +29,13 @@ public class AccountRegister : JsonObject public string UniqueIdentifier { get; set; } [JsonProperty("email")] - public string Email {get; set;} + public string Email { get; set; } [JsonProperty("username")] - public string Username {get; set;} + public string Username { get; set; } [JsonProperty("password")] - public string Password {get; set;} + public string Password { get; set; } } @@ -58,36 +58,36 @@ public class AccountRegisterResponse : JsonObject public class AccountRegisterDetails : JsonObject { [JsonProperty("unique_identifier")] - public string UniqueIdentifier {get; set;} + public string UniqueIdentifier { get; set; } [JsonProperty("email")] - public string Email {get; set;} + public string Email { get; set; } [JsonProperty("username")] - public string Username {get; set;} + public string Username { get; set; } } [Serializable] public class AccountLoginResponse : JsonObject { [JsonProperty("token")] - public string Token {get; set;} + public string Token { get; set; } [JsonProperty("account")] - public AccountGetResponse Account {get; set;} + public AccountGetResponse Account { get; set; } } [Serializable] public class AccountGetResponse : JsonObject { [JsonProperty("unique_identifier")] - public string UniqueIdentifier {get; set;} + public string UniqueIdentifier { get; set; } [JsonProperty("username")] - public string Username {get; set;} + public string Username { get; set; } [JsonProperty("is_verified")] - public bool IsVerified {get; set;} + public bool IsVerified { get; set; } } [Serializable] @@ -113,17 +113,17 @@ public class AccountResendEmailConfirmationRequest : JsonObject public class AccountLoginCredentials : JsonObject { [JsonProperty("email")] - public string Email {get; set;} + public string Email { get; set; } [JsonProperty("password")] - public string Password {get; set;} + public string Password { get; set; } } -public class ApiResult: JsonObject where T : JsonObject +public class ApiResult : JsonObject where T : JsonObject { - public HttpStatusCode StatusCode { get; set; } - public T Data { get; set; } - public ApiHttpException Exception { get; set; } + public HttpStatusCode StatusCode { get; set; } + public T Data { get; set; } + public ApiHttpException Exception { get; set; } public bool IsSuccess => Exception == null; diff --git a/UnitystationLauncher/Services/ApiServer.cs b/UnitystationLauncher/Services/ApiServer.cs index d8c36833..a3c8f2ca 100644 --- a/UnitystationLauncher/Services/ApiServer.cs +++ b/UnitystationLauncher/Services/ApiServer.cs @@ -88,7 +88,7 @@ private static async Task> Send(HttpRequestMessage request) wher HttpClient Client = new HttpClient(); HttpResponseMessage response = await Client.SendAsync(request); string responseBody = await response.Content.ReadAsStringAsync(); - + if (response.IsSuccessStatusCode == false) { if (TryGetApiRequestException(responseBody, response.StatusCode, out ApiRequestException requestException)) diff --git a/UnitystationLauncher/Services/AuthService.cs b/UnitystationLauncher/Services/AuthService.cs index ee079371..9089f13a 100644 --- a/UnitystationLauncher/Services/AuthService.cs +++ b/UnitystationLauncher/Services/AuthService.cs @@ -30,12 +30,12 @@ public AuthService(HttpClient http, IAuthProvider IAuthProvider, IPreferencesSer _preferencesService = preferencesService; LoadAuthSettings(); } - + private string AuthSettingsPath => Path.Combine(_preferencesService.GetPreferences().InstallationPath, "authSettings.json"); public AccountLoginResponse? AccountLoginResponse; - + private void LoadAuthSettings() { @@ -77,14 +77,14 @@ public void SendForgotPasswordEmail(string email) } internal Task SignInWithEmailAndPasswordAsync(string email, string password) - { - return _IAuthProvider.SignInWithEmailAndPasswordAsync(email, password); + { + return _IAuthProvider.SignInWithEmailAndPasswordAsync(email, password); } - - - - - internal async Task CreateAccountAsync(string userId,string username, string email, string password) + + + + + internal async Task CreateAccountAsync(string userId, string username, string email, string password) { // Client-side check for disposable email address. const string url = @@ -129,19 +129,19 @@ internal async Task CreateAccountAsync(string userId,strin { throw new InvalidOperationException("The email domain provided by the user is on our blacklist."); } - + ApiResult registerResponse = await _IAuthProvider.Register(userId, email, username, password); if (registerResponse.IsSuccess == false) { throw new InvalidOperationException("Failed to register account"); } - + return null; } - - + + public async Task GetCustomTokenAsync(string refreshToken) { try @@ -163,7 +163,7 @@ public async Task GetCustomTokenAsync(string refreshToken) Log.Error(e, "Failed when sending token validation request"); return ""; } - + } public async Task SignOutUserAsync() diff --git a/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs b/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs index 3f590648..61ac5f6c 100644 --- a/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs +++ b/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs @@ -16,15 +16,15 @@ public Task> Register( string uniqueIdentifier, string emailAddress, string username, string password); public Task> ResendEmailConfirmation(string email); - - public Task> SendForgotPasswordEmail(string email); + + public Task> SendForgotPasswordEmail(string email); } public class OfficialCentralCommandAuthentication : IAuthProvider -{ - +{ + public static string Host => ApiUrls.ApiBaseUrlLogin; - public static UriBuilder UriBuilder = new( Host); + public static UriBuilder UriBuilder = new(Host); public static Uri GetUri(string endpoint, string queries = null) { @@ -37,7 +37,7 @@ public static Uri GetUri(string endpoint, string queries = null) return UriBuilder.Uri; } - + public async Task> Login(string token) { AccountLoginToken requestBody = new() @@ -54,7 +54,7 @@ public async Task> Login(string token) return response; } - + public static async Task> Login(string emailAddress, string password) { AccountLoginCredentials requestBody = new() @@ -72,13 +72,13 @@ public static async Task> Login(string emailAddr return response; } - + public async Task SignInWithEmailAndPasswordAsync(string emailAddress, string password) { ApiResult loginResponse = await Login(emailAddress, password); - + AccountLoginResponse account = loginResponse.Data; - + return account; } public async Task Logout(string token, bool destroyAllSessions = false) // TODO: but no response? @@ -92,8 +92,8 @@ public async Task Logout(string token, bool destroyAllSessions = fal return response; } - - public async Task> ResendEmailConfirmation(string email) + + public async Task> ResendEmailConfirmation(string email) { AccountResendEmailConfirmationRequest requestBody = new() { @@ -103,7 +103,7 @@ public async Task> ResendEmailConfirmation(string email) var response = await ApiServer.Post(GetUri("resend-account-confirmation"), requestBody); return response; } - + public async Task> Register( string uniqueIdentifier, string emailAddress, string username, string password) { @@ -149,7 +149,7 @@ public async Task> SendForgotPasswordEmail(string email) Console.WriteLine(e); throw; } - - + + } } \ No newline at end of file diff --git a/UnitystationLauncher/ViewModels/ForgotPasswordViewModel.cs b/UnitystationLauncher/ViewModels/ForgotPasswordViewModel.cs index fe1b6983..05485b47 100644 --- a/UnitystationLauncher/ViewModels/ForgotPasswordViewModel.cs +++ b/UnitystationLauncher/ViewModels/ForgotPasswordViewModel.cs @@ -61,7 +61,7 @@ void TrySendResetPassword() public override void Refresh() { - + } public LoginViewModel ReturnToLogin() diff --git a/UnitystationLauncher/ViewModels/LoginStatusViewModel.cs b/UnitystationLauncher/ViewModels/LoginStatusViewModel.cs index c5251ea1..1f7a55e7 100644 --- a/UnitystationLauncher/ViewModels/LoginStatusViewModel.cs +++ b/UnitystationLauncher/ViewModels/LoginStatusViewModel.cs @@ -51,8 +51,8 @@ public LoginStatusViewModel(AuthService authService, Lazy lau } } - - + + public string? ResendEmailAddress { get => _resendEmailAddress; @@ -130,7 +130,7 @@ await _authService.SignInWithEmailAndPasswordAsync( "and try again."; signInSuccess = false; } - + _authService.LoginMsg = null; @@ -153,7 +153,7 @@ public void OnResend() FailedMessage = "Please enter a valid email address to resend verification."; return; } - + _authService.ResendVerificationEmail(ResendEmailAddress); ResendClicked = true; FailedMessage = "A new verification email has been sent to:\r\n" + @@ -173,7 +173,7 @@ public LauncherViewModel SignInComplete() public override void Refresh() { - + } } } \ No newline at end of file diff --git a/UnitystationLauncher/ViewModels/LoginViewModel.cs b/UnitystationLauncher/ViewModels/LoginViewModel.cs index f6a49cc0..da838670 100644 --- a/UnitystationLauncher/ViewModels/LoginViewModel.cs +++ b/UnitystationLauncher/ViewModels/LoginViewModel.cs @@ -89,7 +89,7 @@ public ForgotPasswordViewModel ForgotPass() public override void Refresh() { - + } async Task CheckForLastLoginAsync() @@ -99,8 +99,8 @@ async Task CheckForLastLoginAsync() async Task SaveLoginEmailAsync() { - // var prefs = await _config.GetPreferencesAsync(); - // prefs.LastLogin = _email; + // var prefs = await _config.GetPreferencesAsync(); + // prefs.LastLogin = _email; } } } diff --git a/UnitystationLauncher/ViewModels/MainWindowViewModel.cs b/UnitystationLauncher/ViewModels/MainWindowViewModel.cs index a9016a0b..bc801f0b 100644 --- a/UnitystationLauncher/ViewModels/MainWindowViewModel.cs +++ b/UnitystationLauncher/ViewModels/MainWindowViewModel.cs @@ -102,7 +102,7 @@ async Task AttemptAuthRefreshAsync() _authService.AttemptingAutoLogin = false; return; } - + _authService.AttemptingAutoLogin = false; _authService.SaveAuthSettings(); Content = _launcherVm.Value; @@ -121,9 +121,9 @@ private void ContentChanged() loginStatusVm.GoBack.Select(vm => (ViewModelBase)vm), loginStatusVm.OpenLauncher.Select(vm => (ViewModelBase)vm)), - LauncherViewModel launcherVm => Observable.Merge( - launcherVm.Logout.Select(vm => (ViewModelBase)vm), - launcherVm.ShowUpdateView.Select(vm => (ViewModelBase)vm)), + LauncherViewModel launcherVm => Observable.Merge( + launcherVm.Logout.Select(vm => (ViewModelBase)vm), + launcherVm.ShowUpdateView.Select(vm => (ViewModelBase)vm)), SignUpViewModel signUpViewModel => Observable.Merge( signUpViewModel.Cancel, diff --git a/UnitystationLauncher/ViewModels/SignUpViewModel.cs b/UnitystationLauncher/ViewModels/SignUpViewModel.cs index 337e9d9c..91e81dbd 100644 --- a/UnitystationLauncher/ViewModels/SignUpViewModel.cs +++ b/UnitystationLauncher/ViewModels/SignUpViewModel.cs @@ -27,7 +27,7 @@ public string UsernameID get => _usernameID; set => this.RaiseAndSetIfChanged(ref _usernameID, value); } - + public string Username { get => _username; @@ -92,14 +92,14 @@ public SignUpViewModel(AuthService authService, Lazy loginVm) x => x.Password, x => x.Username, x => x.UsernameID, - (u, p, i,m) => + (u, p, i, m) => !string.IsNullOrWhiteSpace(u) && !string.IsNullOrWhiteSpace(p) && p.Length > 6 && !string.IsNullOrEmpty(i) && !string.IsNullOrEmpty(m)); - - + + Submit = ReactiveCommand.CreateFromTask( UserCreateAsync, possibleCredentials); @@ -111,7 +111,7 @@ public SignUpViewModel(AuthService authService, Lazy loginVm) public override void Refresh() { - + } public async Task UserCreateAsync() From 81c0081c4816f369bae41a3f74331e0d2c9d9ee8 Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Sat, 19 Jul 2025 19:52:41 +0100 Subject: [PATCH 05/26] picttures --- UnitystationLauncher/ViewModels/LoginViewModel.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/UnitystationLauncher/ViewModels/LoginViewModel.cs b/UnitystationLauncher/ViewModels/LoginViewModel.cs index da838670..530127ef 100644 --- a/UnitystationLauncher/ViewModels/LoginViewModel.cs +++ b/UnitystationLauncher/ViewModels/LoginViewModel.cs @@ -44,8 +44,6 @@ public LoginViewModel( ForgotPw = ReactiveCommand.Create( ForgotPass); - - RxApp.MainThreadScheduler.ScheduleAsync((scheduler, ct) => CheckForLastLoginAsync()); } public string Email @@ -72,8 +70,6 @@ public async Task UserLoginAsync() Pass = Password }; - await SaveLoginEmailAsync(); - return _loginStatusVm.Value; } @@ -91,16 +87,5 @@ public override void Refresh() { } - - async Task CheckForLastLoginAsync() - { - //Email = (await _config.GetPreferencesAsync()).LastLogin ?? ""; - } - - async Task SaveLoginEmailAsync() - { - // var prefs = await _config.GetPreferencesAsync(); - // prefs.LastLogin = _email; - } } } From 252c3881b490db2541422e0620da99b3bd574839 Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Sat, 19 Jul 2025 20:07:55 +0100 Subject: [PATCH 06/26] remove unused user model --- UnitystationLauncher/Models/User.cs | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 UnitystationLauncher/Models/User.cs diff --git a/UnitystationLauncher/Models/User.cs b/UnitystationLauncher/Models/User.cs deleted file mode 100644 index 9cc34a53..00000000 --- a/UnitystationLauncher/Models/User.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace UnitystationLauncher.Models; - -public class User -{ - public string Email; - public string LocalId; -} \ No newline at end of file From b9e608c8b953ba6f737b1462e65c876705bd6355 Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Wed, 30 Jul 2025 22:34:50 +0100 Subject: [PATCH 07/26] Adds new authentication flow for Servers --- .../Models/Api/CharacterTokenResponse.cs | 6 + UnitystationLauncher/Models/Api/Server.cs | 5 +- .../ServerConnectionAuthenticationRequest.cs | 11 ++ .../Models/AuthenticationStuff.cs | 1 + UnitystationLauncher/Models/Installation.cs | 2 + UnitystationLauncher/Services/AuthService.cs | 11 ++ .../Services/InstallationService.cs | 33 +++++- .../OfficialCentralCommandAuthentication.cs | 55 +++++++++ .../Services/ServerAuthenticationService.cs | 110 ++++++++++++++++++ 9 files changed, 227 insertions(+), 7 deletions(-) create mode 100644 UnitystationLauncher/Models/Api/CharacterTokenResponse.cs create mode 100644 UnitystationLauncher/Models/Api/ServerConnectionAuthenticationRequest.cs create mode 100644 UnitystationLauncher/Services/ServerAuthenticationService.cs diff --git a/UnitystationLauncher/Models/Api/CharacterTokenResponse.cs b/UnitystationLauncher/Models/Api/CharacterTokenResponse.cs new file mode 100644 index 00000000..a32ea984 --- /dev/null +++ b/UnitystationLauncher/Models/Api/CharacterTokenResponse.cs @@ -0,0 +1,6 @@ +namespace UnitystationLauncher.Models.Api; + +public class CharacterTokenResponse : JsonObject +{ + public string CharacterToken { get; set; } +} \ No newline at end of file diff --git a/UnitystationLauncher/Models/Api/Server.cs b/UnitystationLauncher/Models/Api/Server.cs index 6bd6dc00..50f9b44f 100644 --- a/UnitystationLauncher/Models/Api/Server.cs +++ b/UnitystationLauncher/Models/Api/Server.cs @@ -31,7 +31,10 @@ public Server(string forkName, int buildVersion, string serverIp, int serverPort public string? LinuxDownload { get; set; } public string GoodFileVersion { get; set; } = string.Empty; - + public string ServerPublicKey { get; set; } + + public string ServerConnectionPublicKey { get; set; } + public (string, int) ForkAndVersion => (ForkName, BuildVersion); public string? GetDownloadUrl(IEnvironmentService environmentService) diff --git a/UnitystationLauncher/Models/Api/ServerConnectionAuthenticationRequest.cs b/UnitystationLauncher/Models/Api/ServerConnectionAuthenticationRequest.cs new file mode 100644 index 00000000..a40440f3 --- /dev/null +++ b/UnitystationLauncher/Models/Api/ServerConnectionAuthenticationRequest.cs @@ -0,0 +1,11 @@ +namespace UnitystationLauncher.Models.Api; + +public class ServerConnectionAuthenticationRequest +{ + public string EncryptedClientFork; + public string EncryptedClientVersion; + public string EncryptedGoodFileVersion; + public string EncryptedSharedSecret; + public string EncryptedAccountID; + public string EncryptedConnectionPublicServerKey; +} \ No newline at end of file diff --git a/UnitystationLauncher/Models/AuthenticationStuff.cs b/UnitystationLauncher/Models/AuthenticationStuff.cs index a91a29a8..4d3a53c4 100644 --- a/UnitystationLauncher/Models/AuthenticationStuff.cs +++ b/UnitystationLauncher/Models/AuthenticationStuff.cs @@ -120,6 +120,7 @@ public class AccountLoginCredentials : JsonObject } public class ApiResult : JsonObject where T : JsonObject + { public HttpStatusCode StatusCode { get; set; } public T Data { get; set; } diff --git a/UnitystationLauncher/Models/Installation.cs b/UnitystationLauncher/Models/Installation.cs index 16f50600..aa78ab04 100644 --- a/UnitystationLauncher/Models/Installation.cs +++ b/UnitystationLauncher/Models/Installation.cs @@ -10,6 +10,8 @@ public class Installation public string? ForkName { get; set; } public int BuildVersion { get; set; } + public string? GoodFileVersion { get; set; } + public string? InstallationPath { get; set; } public DateTime LastPlayedDate { get; set; } diff --git a/UnitystationLauncher/Services/AuthService.cs b/UnitystationLauncher/Services/AuthService.cs index 9089f13a..b58e4a4c 100644 --- a/UnitystationLauncher/Services/AuthService.cs +++ b/UnitystationLauncher/Services/AuthService.cs @@ -10,6 +10,7 @@ using Serilog; using UnitystationLauncher.Constants; using UnitystationLauncher.Models; +using UnitystationLauncher.Models.Api; using UnitystationLauncher.Models.ConfigFile; using UnitystationLauncher.Services.Interface; @@ -66,6 +67,16 @@ public void SaveAuthSettings() } } + public async Task GenerateCharacterSheetTokenForFork(string Fork) + { + return (await _IAuthProvider.GenerateCharacterSheetTokenForFork(AccountLoginResponse.Token, Fork)).Data; + } + + public void RegisterJoiningServerWithSecret(string SharedSecret) + { + _IAuthProvider.SendRegisterSharedSecret(AccountLoginResponse.Token, SharedSecret); + } + public void ResendVerificationEmail(string email) { _IAuthProvider.ResendEmailConfirmation(email); diff --git a/UnitystationLauncher/Services/InstallationService.cs b/UnitystationLauncher/Services/InstallationService.cs index befb8638..58d1ea17 100644 --- a/UnitystationLauncher/Services/InstallationService.cs +++ b/UnitystationLauncher/Services/InstallationService.cs @@ -43,9 +43,15 @@ public class InstallationService : IInstallationService private readonly string _installationsJsonFilePath; private readonly ITTSService _TTSVersionService; + public readonly IServerAuthenticationService _IServerAuthenticationService; + private readonly AuthService _authService; + public InstallationService(HttpClient httpClient, IPreferencesService preferencesService, IEnvironmentService environmentService, IServerService serverService, ICodeScanService codeScanService, - ICodeScanConfigService codeScanConfigService, ITTSService ITTSVersionService) + ICodeScanConfigService codeScanConfigService, ITTSService ITTSVersionService, + IServerAuthenticationService IServerAuthenticationService, + AuthService authService + ) { _httpClient = httpClient; _preferencesService = preferencesService; @@ -54,7 +60,8 @@ public InstallationService(HttpClient httpClient, IPreferencesService preference _codeScanService = codeScanService; _codeScanConfigService = codeScanConfigService; _TTSVersionService = ITTSVersionService; - + _IServerAuthenticationService = IServerAuthenticationService; + _authService = authService; _downloads = new(); _installationsJsonFilePath = Path.Combine(_environmentService.GetUserdataDirectory(), "installations.json"); @@ -173,7 +180,7 @@ public List GetInstallations() EnsureExecutableFlagOnUnixSystems(executable); - string arguments = GetArguments(server, port); + string arguments = GetArguments(installation, server, port); ProcessStartInfo? startInfo = _environmentService.GetGameProcessStartInfo(executable, arguments); if (startInfo == null) @@ -418,12 +425,25 @@ private static (bool, string) CanStartDownload(Download download) return (true, string.Empty); } - private static string GetArguments(string? server, long? port) + private string GetArguments(Installation Installation, string? server, long? port) { string arguments = string.Empty; - if (!string.IsNullOrWhiteSpace(server)) + if (string.IsNullOrWhiteSpace(server) == false) { + //TODO Asynchronous!!! + var Arguments = _IServerAuthenticationService.AuthenticateWithServer(server, Installation).Result; + + var AccountID = _authService.AccountLoginResponse.Account.UniqueIdentifier; + var Username = _authService.AccountLoginResponse.Account.Username; + + arguments += $"-AccountID {AccountID}"; + arguments += $"-Username {Username}"; + foreach (var Argument in Arguments) + { + arguments += $"{Argument.Key} {Argument.Value}"; + } + arguments += $"--server {server}"; if (port.HasValue) @@ -512,7 +532,8 @@ void ScanLogs(ScanLog log) ForkName = download.ForkName, InstallationId = Guid.NewGuid(), InstallationPath = download.InstallPath, - LastPlayedDate = DateTime.Now + LastPlayedDate = DateTime.Now, + GoodFileVersion = download.GoodFileVersion }); WriteInstallations(); diff --git a/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs b/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs index 61ac5f6c..7e26fde1 100644 --- a/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs +++ b/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using UnitystationLauncher.Constants; using UnitystationLauncher.Models; +using UnitystationLauncher.Models.Api; namespace UnitystationLauncher.Services; @@ -18,6 +19,10 @@ public Task> Register( public Task> ResendEmailConfirmation(string email); public Task> SendForgotPasswordEmail(string email); + + public Task> SendRegisterSharedSecret(string token, string SharedSecret); + + public Task> GenerateCharacterSheetTokenForFork(string token, string ForkName); } public class OfficialCentralCommandAuthentication : IAuthProvider @@ -149,7 +154,57 @@ public async Task> SendForgotPasswordEmail(string email) Console.WriteLine(e); throw; } + } + public Task> SendRegisterSharedSecret(string token, string SharedSecret) + { + try + { + // var requestBody = new ForgotPasswordModel + // { + // Email = email, + // }; + // + // var response = await ApiServer.Post(GetUri("reset-password/"), requestBody); + // + // if (!response.IsSuccess) + // { + // throw response.Exception!; + // } + // + // return response; + return null; + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + public Task> GenerateCharacterSheetTokenForFork(string token, string ForkName) + { + try + { + // var requestBody = new ForgotPasswordModel + // { + // Email = email, + // }; + // + // var response = await ApiServer.Post(GetUri("reset-password/"), requestBody); + // + // if (!response.IsSuccess) + // { + // throw response.Exception!; + // } + // + // return response; + return null; + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } } } \ No newline at end of file diff --git a/UnitystationLauncher/Services/ServerAuthenticationService.cs b/UnitystationLauncher/Services/ServerAuthenticationService.cs new file mode 100644 index 00000000..458815dd --- /dev/null +++ b/UnitystationLauncher/Services/ServerAuthenticationService.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Security.Authentication; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Serilog; +using UnitystationLauncher.Models; +using UnitystationLauncher.Models.Api; +using System.Security.Cryptography; + +namespace UnitystationLauncher.Services; + +public interface IServerAuthenticationService +{ + public Task GetServerInfoByIP(string IP); + + public Task< Dictionary> AuthenticateWithServer(string IP, Installation Installation); + +} + +public class ServerAuthenticationService : IServerAuthenticationService +{ + + public ServerAuthenticationService(AuthService AuthService) + { + _AuthService = AuthService; + } + + + public readonly AuthService _AuthService; + private readonly HttpClient _httpClient = new HttpClient(); + + public async Task GetServerInfoByIP(string IP) + { + string Port = "7778"; + string url = $"http://{IP}:{Port}/"; + + var response = await _httpClient.GetAsync(url); + response.EnsureSuccessStatusCode(); + + string content = await response.Content.ReadAsStringAsync(); + var serverInfo = JsonConvert.DeserializeObject(content); + + return serverInfo; + } + + public async Task< Dictionary> AuthenticateWithServer(string IP, Installation Installation) + { + + var Info = await GetServerInfoByIP(IP); + string base64PublicKey = Info.ServerPublicKey; + byte[] publicKeyBytes = Convert.FromBase64String(base64PublicKey); + + using RSA rsa = RSA.Create(); + rsa.ImportRSAPublicKey(publicKeyBytes, out _); + + byte[] sharedSecret = new byte[32]; // 256-bit key + RandomNumberGenerator.Fill(sharedSecret); + + // Optional: convert to Base64 if you want to transmit/store it + string base64Secret = Convert.ToBase64String(sharedSecret); + + var ToSend = new ServerConnectionAuthenticationRequest() + { + EncryptedConnectionPublicServerKey = EncryptString(rsa, Info.ServerConnectionPublicKey), + EncryptedClientVersion = EncryptString(rsa, Installation.BuildVersion.ToString()), + EncryptedGoodFileVersion = EncryptString(rsa, Installation.GoodFileVersion.ToString()), + EncryptedClientFork = EncryptString(rsa, Installation.ForkName.ToString()), + EncryptedSharedSecret = EncryptString(rsa, base64Secret), + EncryptedAccountID = EncryptString(rsa, _AuthService.AccountLoginResponse.Account.UniqueIdentifier) + }; + + + // Serialize the object to JSON + string json = JsonConvert.SerializeObject(ToSend); + + // Wrap it in a StringContent with JSON media type + var content = new StringContent(json, Encoding.UTF8, "application/json"); + + string Port = "7778"; + string url = $"http://{IP}:{Port}/"; + + var response = await _httpClient.PostAsync(url, content); + response.EnsureSuccessStatusCode(); + + string contentBack = await response.Content.ReadAsStringAsync(); + if (contentBack != "OK") + { + throw new AuthenticationException(contentBack +$" When trying to authenticate with {url}"); + } + + _AuthService.RegisterJoiningServerWithSecret(base64Secret); + var CharacterToken = await _AuthService.GenerateCharacterSheetTokenForFork(Installation.ForkName); + + return new Dictionary() + { + { "-CharacterToken", CharacterToken.CharacterToken}, + { "-SharedSecret", base64Secret}, + { "-ServerPublicConnectionKey", Info.ServerConnectionPublicKey}, + }; + } + + private string EncryptString(RSA rsa, string ToEncrypt) + { + return Convert.ToBase64String(rsa.Encrypt(Encoding.UTF8.GetBytes(ToEncrypt), RSAEncryptionPadding.OaepSHA256)); + } +} \ No newline at end of file From 887d5496be563303cc1179586e7b96043244d6a4 Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Thu, 31 Jul 2025 21:17:58 +0100 Subject: [PATCH 08/26] fix --- .../Services/ServerAuthenticationService.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/UnitystationLauncher/Services/ServerAuthenticationService.cs b/UnitystationLauncher/Services/ServerAuthenticationService.cs index 458815dd..b98706b3 100644 --- a/UnitystationLauncher/Services/ServerAuthenticationService.cs +++ b/UnitystationLauncher/Services/ServerAuthenticationService.cs @@ -32,7 +32,9 @@ public ServerAuthenticationService(AuthService AuthService) public readonly AuthService _AuthService; private readonly HttpClient _httpClient = new HttpClient(); - + + private readonly SHA512 SHA512 = SHA512.Create(); + public async Task GetServerInfoByIP(string IP) { string Port = "7778"; @@ -92,7 +94,9 @@ public async Task< Dictionary> AuthenticateWithServer(string IP, throw new AuthenticationException(contentBack +$" When trying to authenticate with {url}"); } - _AuthService.RegisterJoiningServerWithSecret(base64Secret); + var SHA512Check = Convert.ToBase64String(SHA512.ComputeHash(Encoding.UTF8.GetBytes(base64Secret + Info.ServerPublicKey))); + _AuthService.RegisterJoiningServerWithSecret(SHA512Check); + var CharacterToken = await _AuthService.GenerateCharacterSheetTokenForFork(Installation.ForkName); return new Dictionary() From 5e8c918e437f55ce91fc6cff69c9d98886411480 Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Thu, 31 Jul 2025 21:58:48 +0100 Subject: [PATCH 09/26] Code fixes --- .../Api/ServerConnectionAuthenticationRequest.cs | 12 ++++++------ UnitystationLauncher/Services/ApiServer.cs | 14 ++++++++------ UnitystationLauncher/Services/AuthService.cs | 13 ++++++++----- .../Services/InstallationService.cs | 2 +- .../OfficialCentralCommandAuthentication.cs | 4 ++-- .../Services/ServerAuthenticationService.cs | 6 +++--- .../ViewModels/ForgotPasswordViewModel.cs | 2 +- .../ViewModels/LoginStatusViewModel.cs | 4 ++-- UnitystationLauncher/ViewModels/LoginViewModel.cs | 2 +- UnitystationLauncher/ViewModels/SignUpViewModel.cs | 2 +- 10 files changed, 33 insertions(+), 28 deletions(-) diff --git a/UnitystationLauncher/Models/Api/ServerConnectionAuthenticationRequest.cs b/UnitystationLauncher/Models/Api/ServerConnectionAuthenticationRequest.cs index a40440f3..29cd673d 100644 --- a/UnitystationLauncher/Models/Api/ServerConnectionAuthenticationRequest.cs +++ b/UnitystationLauncher/Models/Api/ServerConnectionAuthenticationRequest.cs @@ -2,10 +2,10 @@ public class ServerConnectionAuthenticationRequest { - public string EncryptedClientFork; - public string EncryptedClientVersion; - public string EncryptedGoodFileVersion; - public string EncryptedSharedSecret; - public string EncryptedAccountID; - public string EncryptedConnectionPublicServerKey; + public string EncryptedClientFork { get; set; } + public string EncryptedClientVersion { get; set; } + public string EncryptedGoodFileVersion { get; set; } + public string EncryptedSharedSecret { get; set; } + public string EncryptedAccountID { get; set; } + public string EncryptedConnectionPublicServerKey { get; set; } } \ No newline at end of file diff --git a/UnitystationLauncher/Services/ApiServer.cs b/UnitystationLauncher/Services/ApiServer.cs index a3c8f2ca..83267ac4 100644 --- a/UnitystationLauncher/Services/ApiServer.cs +++ b/UnitystationLauncher/Services/ApiServer.cs @@ -16,13 +16,15 @@ namespace UnitystationLauncher.Services; /// public static class ApiServer { + public const string AuthenticationHeaderValue = "Token"; + internal static async Task> Get(Uri uri, string token = default) where T : JsonObject { HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri); if (token != default) { - request.Headers.Authorization = new AuthenticationHeaderValue("Token", token); + request.Headers.Authorization = new AuthenticationHeaderValue(AuthenticationHeaderValue, token); } return await Send(request); @@ -35,12 +37,12 @@ internal static async Task> Post(Uri uri, JsonObject body, strin if (body is ITokenAuthable authable) { - request.Headers.Authorization = new AuthenticationHeaderValue("Token", authable.Token); + request.Headers.Authorization = new AuthenticationHeaderValue(AuthenticationHeaderValue, authable.Token); } if (token != default) { - request.Headers.Authorization = new AuthenticationHeaderValue("Token", token); + request.Headers.Authorization = new AuthenticationHeaderValue(AuthenticationHeaderValue, token); } string sss = JsonConvert.SerializeObject(body); @@ -55,12 +57,12 @@ internal static async Task> Put(Uri uri, JsonObject body, string if (body is ITokenAuthable authable) { - request.Headers.Authorization = new AuthenticationHeaderValue("Token", authable.Token); + request.Headers.Authorization = new AuthenticationHeaderValue(AuthenticationHeaderValue, authable.Token); } if (token != default) { - request.Headers.Authorization = new AuthenticationHeaderValue("Token", token); + request.Headers.Authorization = new AuthenticationHeaderValue(AuthenticationHeaderValue, token); } var sss = JsonConvert.SerializeObject(body); @@ -75,7 +77,7 @@ internal static async Task> Delete(Uri uri, string token = defau if (token != default) { - request.Headers.Authorization = new AuthenticationHeaderValue("Token", token); + request.Headers.Authorization = new AuthenticationHeaderValue(AuthenticationHeaderValue, token); } return await Send(request); diff --git a/UnitystationLauncher/Services/AuthService.cs b/UnitystationLauncher/Services/AuthService.cs index b58e4a4c..17b7303d 100644 --- a/UnitystationLauncher/Services/AuthService.cs +++ b/UnitystationLauncher/Services/AuthService.cs @@ -21,7 +21,7 @@ public class AuthService private readonly HttpClient _http; public LoginMsg? LoginMsg { get; set; } public bool AttemptingAutoLogin { get; set; } - public IAuthProvider _IAuthProvider; + private IAuthProvider _IAuthProvider; private readonly IPreferencesService _preferencesService; public AuthService(HttpClient http, IAuthProvider IAuthProvider, IPreferencesService preferencesService) @@ -35,8 +35,11 @@ public AuthService(HttpClient http, IAuthProvider IAuthProvider, IPreferencesSer private string AuthSettingsPath => Path.Combine(_preferencesService.GetPreferences().InstallationPath, "authSettings.json"); - public AccountLoginResponse? AccountLoginResponse; + public AccountLoginResponse? AccountLoginResponse => accountLoginResponse; + private AccountLoginResponse? accountLoginResponse; + + private void LoadAuthSettings() { @@ -46,7 +49,7 @@ private void LoadAuthSettings() { var json = File.ReadAllText(AuthSettingsPath); var AccountLoginResponseA = JsonSerializer.Deserialize(json); - AccountLoginResponse = AccountLoginResponseA; + accountLoginResponse = AccountLoginResponseA; } } catch (Exception) @@ -165,7 +168,7 @@ public async Task GetCustomTokenAsync(string refreshToken) } else { - AccountLoginResponse = Response.Data; + accountLoginResponse = Response.Data; return Response.Data.Token; } } @@ -180,7 +183,7 @@ public async Task GetCustomTokenAsync(string refreshToken) public async Task SignOutUserAsync() { await _IAuthProvider.Logout(AccountLoginResponse.Token); - AccountLoginResponse = null; + accountLoginResponse = null; } } diff --git a/UnitystationLauncher/Services/InstallationService.cs b/UnitystationLauncher/Services/InstallationService.cs index 58d1ea17..aa477a95 100644 --- a/UnitystationLauncher/Services/InstallationService.cs +++ b/UnitystationLauncher/Services/InstallationService.cs @@ -43,7 +43,7 @@ public class InstallationService : IInstallationService private readonly string _installationsJsonFilePath; private readonly ITTSService _TTSVersionService; - public readonly IServerAuthenticationService _IServerAuthenticationService; + private readonly IServerAuthenticationService _IServerAuthenticationService; private readonly AuthService _authService; public InstallationService(HttpClient httpClient, IPreferencesService preferencesService, diff --git a/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs b/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs index 7e26fde1..40c661db 100644 --- a/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs +++ b/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs @@ -8,7 +8,7 @@ namespace UnitystationLauncher.Services; public interface IAuthProvider { - public Task SignInWithEmailAndPasswordAsync(string email, string password); + public Task SignInWithEmailAndPasswordAsync(string emailAddress, string password); public Task> Login(string token); public Task Logout(string token, bool destroyAllSessions = false); @@ -29,7 +29,7 @@ public class OfficialCentralCommandAuthentication : IAuthProvider { public static string Host => ApiUrls.ApiBaseUrlLogin; - public static UriBuilder UriBuilder = new(Host); + private static UriBuilder UriBuilder = new(Host); public static Uri GetUri(string endpoint, string queries = null) { diff --git a/UnitystationLauncher/Services/ServerAuthenticationService.cs b/UnitystationLauncher/Services/ServerAuthenticationService.cs index b98706b3..035b6567 100644 --- a/UnitystationLauncher/Services/ServerAuthenticationService.cs +++ b/UnitystationLauncher/Services/ServerAuthenticationService.cs @@ -30,7 +30,7 @@ public ServerAuthenticationService(AuthService AuthService) } - public readonly AuthService _AuthService; + private readonly AuthService _AuthService; private readonly HttpClient _httpClient = new HttpClient(); private readonly SHA512 SHA512 = SHA512.Create(); @@ -65,7 +65,7 @@ public async Task< Dictionary> AuthenticateWithServer(string IP, // Optional: convert to Base64 if you want to transmit/store it string base64Secret = Convert.ToBase64String(sharedSecret); - var ToSend = new ServerConnectionAuthenticationRequest() + var ToSend = new ServerConnectionAuthenticationRequest { EncryptedConnectionPublicServerKey = EncryptString(rsa, Info.ServerConnectionPublicKey), EncryptedClientVersion = EncryptString(rsa, Installation.BuildVersion.ToString()), @@ -99,7 +99,7 @@ public async Task< Dictionary> AuthenticateWithServer(string IP, var CharacterToken = await _AuthService.GenerateCharacterSheetTokenForFork(Installation.ForkName); - return new Dictionary() + return new Dictionary { { "-CharacterToken", CharacterToken.CharacterToken}, { "-SharedSecret", base64Secret}, diff --git a/UnitystationLauncher/ViewModels/ForgotPasswordViewModel.cs b/UnitystationLauncher/ViewModels/ForgotPasswordViewModel.cs index 05485b47..cada4884 100644 --- a/UnitystationLauncher/ViewModels/ForgotPasswordViewModel.cs +++ b/UnitystationLauncher/ViewModels/ForgotPasswordViewModel.cs @@ -61,7 +61,7 @@ void TrySendResetPassword() public override void Refresh() { - + // Do nothing } public LoginViewModel ReturnToLogin() diff --git a/UnitystationLauncher/ViewModels/LoginStatusViewModel.cs b/UnitystationLauncher/ViewModels/LoginStatusViewModel.cs index 1f7a55e7..ac2d163e 100644 --- a/UnitystationLauncher/ViewModels/LoginStatusViewModel.cs +++ b/UnitystationLauncher/ViewModels/LoginStatusViewModel.cs @@ -43,7 +43,7 @@ public LoginStatusViewModel(AuthService authService, Lazy lau if (!authService.AttemptingAutoLogin) { - RxApp.MainThreadScheduler.ScheduleAsync((scheduler, ct) => UserLoginAsync()); + RxApp.MainThreadScheduler.ScheduleAsync((_, _) => UserLoginAsync()); } else { @@ -173,7 +173,7 @@ public LauncherViewModel SignInComplete() public override void Refresh() { - + // Do nothing } } } \ No newline at end of file diff --git a/UnitystationLauncher/ViewModels/LoginViewModel.cs b/UnitystationLauncher/ViewModels/LoginViewModel.cs index 530127ef..1582171e 100644 --- a/UnitystationLauncher/ViewModels/LoginViewModel.cs +++ b/UnitystationLauncher/ViewModels/LoginViewModel.cs @@ -85,7 +85,7 @@ public ForgotPasswordViewModel ForgotPass() public override void Refresh() { - + // Do nothing } } } diff --git a/UnitystationLauncher/ViewModels/SignUpViewModel.cs b/UnitystationLauncher/ViewModels/SignUpViewModel.cs index 91e81dbd..8dcb3af8 100644 --- a/UnitystationLauncher/ViewModels/SignUpViewModel.cs +++ b/UnitystationLauncher/ViewModels/SignUpViewModel.cs @@ -111,7 +111,7 @@ public SignUpViewModel(AuthService authService, Lazy loginVm) public override void Refresh() { - + // Do nothing } public async Task UserCreateAsync() From 9dc6a8a0ba53cdcdddeaa0ebb1cd66be132338c3 Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Thu, 31 Jul 2025 22:01:57 +0100 Subject: [PATCH 10/26] fiz --- UnitystationLauncher/Services/AuthService.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/UnitystationLauncher/Services/AuthService.cs b/UnitystationLauncher/Services/AuthService.cs index 17b7303d..7bfe61d8 100644 --- a/UnitystationLauncher/Services/AuthService.cs +++ b/UnitystationLauncher/Services/AuthService.cs @@ -35,7 +35,17 @@ public AuthService(HttpClient http, IAuthProvider IAuthProvider, IPreferencesSer private string AuthSettingsPath => Path.Combine(_preferencesService.GetPreferences().InstallationPath, "authSettings.json"); - public AccountLoginResponse? AccountLoginResponse => accountLoginResponse; + public AccountLoginResponse? AccountLoginResponse + { + get + { + return accountLoginResponse; + } + set + { + accountLoginResponse = value; + } + } private AccountLoginResponse? accountLoginResponse; From 5c7dd9d2ee008361c13067980f3b05bc0d446e06 Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Thu, 31 Jul 2025 22:11:19 +0100 Subject: [PATCH 11/26] changes --- UnitystationLauncher/Services/AuthService.cs | 23 +++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/UnitystationLauncher/Services/AuthService.cs b/UnitystationLauncher/Services/AuthService.cs index 7bfe61d8..f4aeb0bd 100644 --- a/UnitystationLauncher/Services/AuthService.cs +++ b/UnitystationLauncher/Services/AuthService.cs @@ -21,7 +21,7 @@ public class AuthService private readonly HttpClient _http; public LoginMsg? LoginMsg { get; set; } public bool AttemptingAutoLogin { get; set; } - private IAuthProvider _IAuthProvider; + private readonly IAuthProvider _IAuthProvider; private readonly IPreferencesService _preferencesService; public AuthService(HttpClient http, IAuthProvider IAuthProvider, IPreferencesService preferencesService) @@ -35,21 +35,8 @@ public AuthService(HttpClient http, IAuthProvider IAuthProvider, IPreferencesSer private string AuthSettingsPath => Path.Combine(_preferencesService.GetPreferences().InstallationPath, "authSettings.json"); - public AccountLoginResponse? AccountLoginResponse - { - get - { - return accountLoginResponse; - } - set - { - accountLoginResponse = value; - } - } - - private AccountLoginResponse? accountLoginResponse; + public AccountLoginResponse? AccountLoginResponse { get; set; } - private void LoadAuthSettings() { @@ -59,7 +46,7 @@ private void LoadAuthSettings() { var json = File.ReadAllText(AuthSettingsPath); var AccountLoginResponseA = JsonSerializer.Deserialize(json); - accountLoginResponse = AccountLoginResponseA; + AccountLoginResponse = AccountLoginResponseA; } } catch (Exception) @@ -178,7 +165,7 @@ public async Task GetCustomTokenAsync(string refreshToken) } else { - accountLoginResponse = Response.Data; + AccountLoginResponse = Response.Data; return Response.Data.Token; } } @@ -193,7 +180,7 @@ public async Task GetCustomTokenAsync(string refreshToken) public async Task SignOutUserAsync() { await _IAuthProvider.Logout(AccountLoginResponse.Token); - accountLoginResponse = null; + AccountLoginResponse = null; } } From c8657b5b53ba11f1eae976e99e798e2485b5def1 Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Thu, 31 Jul 2025 22:28:36 +0100 Subject: [PATCH 12/26] format --- .../Models/Api/CharacterTokenResponse.cs | 2 +- UnitystationLauncher/Models/Api/Server.cs | 8 ++-- UnitystationLauncher/Models/Installation.cs | 2 +- UnitystationLauncher/Services/ApiServer.cs | 2 +- UnitystationLauncher/Services/AuthService.cs | 48 +------------------ .../Services/InstallationService.cs | 4 +- .../OfficialCentralCommandAuthentication.cs | 4 +- .../Services/ServerAuthenticationService.cs | 34 ++++++------- .../ViewModels/ForgotPasswordViewModel.cs | 2 +- 9 files changed, 31 insertions(+), 75 deletions(-) diff --git a/UnitystationLauncher/Models/Api/CharacterTokenResponse.cs b/UnitystationLauncher/Models/Api/CharacterTokenResponse.cs index a32ea984..fc5d1235 100644 --- a/UnitystationLauncher/Models/Api/CharacterTokenResponse.cs +++ b/UnitystationLauncher/Models/Api/CharacterTokenResponse.cs @@ -3,4 +3,4 @@ public class CharacterTokenResponse : JsonObject { public string CharacterToken { get; set; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/UnitystationLauncher/Models/Api/Server.cs b/UnitystationLauncher/Models/Api/Server.cs index 50f9b44f..206b4f3b 100644 --- a/UnitystationLauncher/Models/Api/Server.cs +++ b/UnitystationLauncher/Models/Api/Server.cs @@ -31,10 +31,10 @@ public Server(string forkName, int buildVersion, string serverIp, int serverPort public string? LinuxDownload { get; set; } public string GoodFileVersion { get; set; } = string.Empty; - public string ServerPublicKey { get; set; } - - public string ServerConnectionPublicKey { get; set; } - + public string ServerPublicKey { get; set; } + + public string ServerConnectionPublicKey { get; set; } + public (string, int) ForkAndVersion => (ForkName, BuildVersion); public string? GetDownloadUrl(IEnvironmentService environmentService) diff --git a/UnitystationLauncher/Models/Installation.cs b/UnitystationLauncher/Models/Installation.cs index aa78ab04..77d07ba3 100644 --- a/UnitystationLauncher/Models/Installation.cs +++ b/UnitystationLauncher/Models/Installation.cs @@ -11,7 +11,7 @@ public class Installation public int BuildVersion { get; set; } public string? GoodFileVersion { get; set; } - + public string? InstallationPath { get; set; } public DateTime LastPlayedDate { get; set; } diff --git a/UnitystationLauncher/Services/ApiServer.cs b/UnitystationLauncher/Services/ApiServer.cs index 83267ac4..4f3646d6 100644 --- a/UnitystationLauncher/Services/ApiServer.cs +++ b/UnitystationLauncher/Services/ApiServer.cs @@ -17,7 +17,7 @@ namespace UnitystationLauncher.Services; public static class ApiServer { public const string AuthenticationHeaderValue = "Token"; - + internal static async Task> Get(Uri uri, string token = default) where T : JsonObject { HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri); diff --git a/UnitystationLauncher/Services/AuthService.cs b/UnitystationLauncher/Services/AuthService.cs index f4aeb0bd..f3aa88b1 100644 --- a/UnitystationLauncher/Services/AuthService.cs +++ b/UnitystationLauncher/Services/AuthService.cs @@ -95,52 +95,8 @@ internal Task SignInWithEmailAndPasswordAsync(string email - internal async Task CreateAccountAsync(string userId, string username, string email, string password) + internal async Task> CreateAccountAsync(string userId, string username, string email, string password) { - // Client-side check for disposable email address. - const string url = - "https://raw.githubusercontent.com/martenson/disposable-email-domains/master/disposable_email_blocklist.conf"; - HttpRequestMessage requestMessage = new(HttpMethod.Get, url); - - CancellationToken cancellationToken = new CancellationTokenSource(60000).Token; - bool isDomainBlacklisted = false; - try - { - HttpResponseMessage response = await _http.SendAsync(requestMessage, cancellationToken); - string msg = await response.Content.ReadAsStringAsync(cancellationToken); - - // Turn msg into a hashset of all domains - using StringReader stringReader = new(msg); - List lines = new(); - - while (await stringReader.ReadLineAsync() is { } line) - { - if (!string.IsNullOrWhiteSpace(line) && !line.TrimStart().StartsWith("//")) - { - lines.Add(line); - } - } - - HashSet blacklist = new(lines, StringComparer.OrdinalIgnoreCase); - - MailAddress address = new(email); - if (blacklist.Contains(address.Host)) - { - // Randomly wait before failing. Might frustrate users who try different disposable emails. - await Task.Delay(new Random().Next(3000, 12000), cancellationToken); - isDomainBlacklisted = true; - } - } - catch (Exception e) - { - Log.Error(e, "Error or timeout in check for email domain blacklist, check has been skipped"); - } - - if (isDomainBlacklisted) - { - throw new InvalidOperationException("The email domain provided by the user is on our blacklist."); - } - ApiResult registerResponse = await _IAuthProvider.Register(userId, email, username, password); if (registerResponse.IsSuccess == false) @@ -148,7 +104,7 @@ internal async Task CreateAccountAsync(string userId, stri throw new InvalidOperationException("Failed to register account"); } - return null; + return registerResponse; } diff --git a/UnitystationLauncher/Services/InstallationService.cs b/UnitystationLauncher/Services/InstallationService.cs index aa477a95..a83a8ef7 100644 --- a/UnitystationLauncher/Services/InstallationService.cs +++ b/UnitystationLauncher/Services/InstallationService.cs @@ -436,14 +436,14 @@ private string GetArguments(Installation Installation, string? server, long? por var AccountID = _authService.AccountLoginResponse.Account.UniqueIdentifier; var Username = _authService.AccountLoginResponse.Account.Username; - + arguments += $"-AccountID {AccountID}"; arguments += $"-Username {Username}"; foreach (var Argument in Arguments) { arguments += $"{Argument.Key} {Argument.Value}"; } - + arguments += $"--server {server}"; if (port.HasValue) diff --git a/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs b/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs index 40c661db..6cfc83d7 100644 --- a/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs +++ b/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs @@ -19,9 +19,9 @@ public Task> Register( public Task> ResendEmailConfirmation(string email); public Task> SendForgotPasswordEmail(string email); - + public Task> SendRegisterSharedSecret(string token, string SharedSecret); - + public Task> GenerateCharacterSheetTokenForFork(string token, string ForkName); } diff --git a/UnitystationLauncher/Services/ServerAuthenticationService.cs b/UnitystationLauncher/Services/ServerAuthenticationService.cs index 035b6567..e6979752 100644 --- a/UnitystationLauncher/Services/ServerAuthenticationService.cs +++ b/UnitystationLauncher/Services/ServerAuthenticationService.cs @@ -16,9 +16,9 @@ namespace UnitystationLauncher.Services; public interface IServerAuthenticationService { public Task GetServerInfoByIP(string IP); - - public Task< Dictionary> AuthenticateWithServer(string IP, Installation Installation); - + + public Task> AuthenticateWithServer(string IP, Installation Installation); + } public class ServerAuthenticationService : IServerAuthenticationService @@ -32,14 +32,14 @@ public ServerAuthenticationService(AuthService AuthService) private readonly AuthService _AuthService; private readonly HttpClient _httpClient = new HttpClient(); - + private readonly SHA512 SHA512 = SHA512.Create(); - + public async Task GetServerInfoByIP(string IP) { string Port = "7778"; string url = $"http://{IP}:{Port}/"; - + var response = await _httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); @@ -49,7 +49,7 @@ public async Task GetServerInfoByIP(string IP) return serverInfo; } - public async Task< Dictionary> AuthenticateWithServer(string IP, Installation Installation) + public async Task> AuthenticateWithServer(string IP, Installation Installation) { var Info = await GetServerInfoByIP(IP); @@ -58,7 +58,7 @@ public async Task< Dictionary> AuthenticateWithServer(string IP, using RSA rsa = RSA.Create(); rsa.ImportRSAPublicKey(publicKeyBytes, out _); - + byte[] sharedSecret = new byte[32]; // 256-bit key RandomNumberGenerator.Fill(sharedSecret); @@ -74,31 +74,31 @@ public async Task< Dictionary> AuthenticateWithServer(string IP, EncryptedSharedSecret = EncryptString(rsa, base64Secret), EncryptedAccountID = EncryptString(rsa, _AuthService.AccountLoginResponse.Account.UniqueIdentifier) }; - - + + // Serialize the object to JSON string json = JsonConvert.SerializeObject(ToSend); // Wrap it in a StringContent with JSON media type var content = new StringContent(json, Encoding.UTF8, "application/json"); - + string Port = "7778"; string url = $"http://{IP}:{Port}/"; - + var response = await _httpClient.PostAsync(url, content); response.EnsureSuccessStatusCode(); string contentBack = await response.Content.ReadAsStringAsync(); if (contentBack != "OK") { - throw new AuthenticationException(contentBack +$" When trying to authenticate with {url}"); + throw new AuthenticationException(contentBack + $" When trying to authenticate with {url}"); } - + var SHA512Check = Convert.ToBase64String(SHA512.ComputeHash(Encoding.UTF8.GetBytes(base64Secret + Info.ServerPublicKey))); _AuthService.RegisterJoiningServerWithSecret(SHA512Check); - - var CharacterToken = await _AuthService.GenerateCharacterSheetTokenForFork(Installation.ForkName); - + + var CharacterToken = await _AuthService.GenerateCharacterSheetTokenForFork(Installation.ForkName); + return new Dictionary { { "-CharacterToken", CharacterToken.CharacterToken}, diff --git a/UnitystationLauncher/ViewModels/ForgotPasswordViewModel.cs b/UnitystationLauncher/ViewModels/ForgotPasswordViewModel.cs index cada4884..2e15a09d 100644 --- a/UnitystationLauncher/ViewModels/ForgotPasswordViewModel.cs +++ b/UnitystationLauncher/ViewModels/ForgotPasswordViewModel.cs @@ -61,7 +61,7 @@ void TrySendResetPassword() public override void Refresh() { - // Do nothing + // Do nothing } public LoginViewModel ReturnToLogin() From 1f14b6ce61e7e81c69cf8d2382bffa27ee25065c Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Thu, 31 Jul 2025 22:47:34 +0100 Subject: [PATCH 13/26] code --- .../PipeHubBuildCommunication.cs | 8 +++++++- .../Api/ServerConnectionAuthenticationRequest.cs | 12 ++++++------ UnitystationLauncher/Models/AuthenticationStuff.cs | 4 ++-- UnitystationLauncher/Services/ApiServer.cs | 8 ++++---- .../Services/OfficialCentralCommandAuthentication.cs | 2 +- .../Services/ServerAuthenticationService.cs | 2 -- 6 files changed, 20 insertions(+), 16 deletions(-) diff --git a/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs b/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs index 488dcfb8..90162301 100644 --- a/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs +++ b/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs @@ -13,7 +13,7 @@ namespace UnitystationLauncher.GameCommunicationPipe; public class PipeHubBuildCommunication : IDisposable { - private static NamedPipeServerStream _serverPipe; + private static NamedPipeServerStream? _serverPipe; private static StreamReader? _reader; private static StreamWriter? _writer; @@ -32,6 +32,12 @@ public PipeHubBuildCommunication() public async Task StartServerPipe() { + if (_serverPipe == null) + { + _serverPipe = new("Unitystation_Hub_Build_Communication", PipeDirection.InOut, 1, + PipeTransmissionMode.Byte, PipeOptions.Asynchronous); + } + await _serverPipe.WaitForConnectionAsync(); _reader = new(_serverPipe); _writer = new(_serverPipe); diff --git a/UnitystationLauncher/Models/Api/ServerConnectionAuthenticationRequest.cs b/UnitystationLauncher/Models/Api/ServerConnectionAuthenticationRequest.cs index 29cd673d..04d97696 100644 --- a/UnitystationLauncher/Models/Api/ServerConnectionAuthenticationRequest.cs +++ b/UnitystationLauncher/Models/Api/ServerConnectionAuthenticationRequest.cs @@ -2,10 +2,10 @@ public class ServerConnectionAuthenticationRequest { - public string EncryptedClientFork { get; set; } - public string EncryptedClientVersion { get; set; } - public string EncryptedGoodFileVersion { get; set; } - public string EncryptedSharedSecret { get; set; } - public string EncryptedAccountID { get; set; } - public string EncryptedConnectionPublicServerKey { get; set; } + public string? EncryptedClientFork { get; set; } + public string? EncryptedClientVersion { get; set; } + public string? EncryptedGoodFileVersion { get; set; } + public string? EncryptedSharedSecret { get; set; } + public string? EncryptedAccountID { get; set; } + public string? EncryptedConnectionPublicServerKey { get; set; } } \ No newline at end of file diff --git a/UnitystationLauncher/Models/AuthenticationStuff.cs b/UnitystationLauncher/Models/AuthenticationStuff.cs index 4d3a53c4..fcc73e66 100644 --- a/UnitystationLauncher/Models/AuthenticationStuff.cs +++ b/UnitystationLauncher/Models/AuthenticationStuff.cs @@ -124,11 +124,11 @@ public class ApiResult : JsonObject where T : JsonObject { public HttpStatusCode StatusCode { get; set; } public T Data { get; set; } - public ApiHttpException Exception { get; set; } + public ApiHttpException? Exception { get; set; } public bool IsSuccess => Exception == null; - private ApiResult(HttpStatusCode statusCode, T data, ApiHttpException exception = null) + private ApiResult(HttpStatusCode statusCode, T data, ApiHttpException? exception = null) { StatusCode = statusCode; Data = data; diff --git a/UnitystationLauncher/Services/ApiServer.cs b/UnitystationLauncher/Services/ApiServer.cs index 4f3646d6..1a59f592 100644 --- a/UnitystationLauncher/Services/ApiServer.cs +++ b/UnitystationLauncher/Services/ApiServer.cs @@ -18,7 +18,7 @@ public static class ApiServer { public const string AuthenticationHeaderValue = "Token"; - internal static async Task> Get(Uri uri, string token = default) where T : JsonObject + internal static async Task> Get(Uri uri, string? token = default) where T : JsonObject { HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri); @@ -30,7 +30,7 @@ internal static async Task> Get(Uri uri, string token = default) return await Send(request); } - internal static async Task> Post(Uri uri, JsonObject body, string token = default) + internal static async Task> Post(Uri uri, JsonObject body, string? token = default) where T : JsonObject { HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri); @@ -50,7 +50,7 @@ internal static async Task> Post(Uri uri, JsonObject body, strin return await Send(request); } - internal static async Task> Put(Uri uri, JsonObject body, string token = default) + internal static async Task> Put(Uri uri, JsonObject body, string? token = default) where T : JsonObject { HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Put, uri); @@ -71,7 +71,7 @@ internal static async Task> Put(Uri uri, JsonObject body, string return await Send(request); } - internal static async Task> Delete(Uri uri, string token = default) where T : JsonObject + internal static async Task> Delete(Uri uri, string? token = default) where T : JsonObject { HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Delete, uri); diff --git a/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs b/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs index 6cfc83d7..d3c52dbc 100644 --- a/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs +++ b/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs @@ -30,7 +30,7 @@ public class OfficialCentralCommandAuthentication : IAuthProvider public static string Host => ApiUrls.ApiBaseUrlLogin; private static UriBuilder UriBuilder = new(Host); - public static Uri GetUri(string endpoint, string queries = null) + public static Uri GetUri(string endpoint, string? queries = null) { UriBuilder.Path = $"/accounts/{endpoint}"; diff --git a/UnitystationLauncher/Services/ServerAuthenticationService.cs b/UnitystationLauncher/Services/ServerAuthenticationService.cs index e6979752..e02e302a 100644 --- a/UnitystationLauncher/Services/ServerAuthenticationService.cs +++ b/UnitystationLauncher/Services/ServerAuthenticationService.cs @@ -6,10 +6,8 @@ using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; -using Serilog; using UnitystationLauncher.Models; using UnitystationLauncher.Models.Api; -using System.Security.Cryptography; namespace UnitystationLauncher.Services; From 925285137558088656fa0da65c6dc9b5d20b885e Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Thu, 31 Jul 2025 22:49:09 +0100 Subject: [PATCH 14/26] format --- .../GameCommunicationPipe/PipeHubBuildCommunication.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs b/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs index 90162301..cdbee241 100644 --- a/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs +++ b/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs @@ -37,7 +37,7 @@ public async Task StartServerPipe() _serverPipe = new("Unitystation_Hub_Build_Communication", PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); } - + await _serverPipe.WaitForConnectionAsync(); _reader = new(_serverPipe); _writer = new(_serverPipe); From 1c24c07d83ab71e64e6d265372cddb0fc8a8b943 Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Sat, 2 Aug 2025 14:25:22 +0100 Subject: [PATCH 15/26] stuff --- .../GameCommunicationPipe/PipeHubBuildCommunication.cs | 4 ++-- UnitystationLauncher/Models/AuthenticationStuff.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs b/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs index cdbee241..b95a9a34 100644 --- a/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs +++ b/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs @@ -150,8 +150,8 @@ The main purpose of this Prompt is to allow the Variable viewer (Variable editin public void Dispose() { - _serverPipe.Close(); - _serverPipe.Dispose(); + _serverPipe?.Close(); + _serverPipe?.Dispose(); _reader?.Close(); _reader?.Dispose(); _writer?.Close(); diff --git a/UnitystationLauncher/Models/AuthenticationStuff.cs b/UnitystationLauncher/Models/AuthenticationStuff.cs index fcc73e66..c0834312 100644 --- a/UnitystationLauncher/Models/AuthenticationStuff.cs +++ b/UnitystationLauncher/Models/AuthenticationStuff.cs @@ -26,16 +26,16 @@ public virtual StringContent ToStringContent() public class AccountRegister : JsonObject { [JsonProperty("unique_identifier")] - public string UniqueIdentifier { get; set; } + public string? UniqueIdentifier { get; set; } [JsonProperty("email")] - public string Email { get; set; } + public string? Email { get; set; } [JsonProperty("username")] - public string Username { get; set; } + public string? Username { get; set; } [JsonProperty("password")] - public string Password { get; set; } + public string? Password { get; set; } } @@ -43,7 +43,7 @@ public class AccountRegister : JsonObject public class ForgotPasswordModel : JsonObject { [JsonProperty("email")] - public string Email { get; set; } + public string? Email { get; set; } } From 4412242e03d34be1089a7029d27de0899d32db76 Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Tue, 5 Aug 2025 23:00:46 +0100 Subject: [PATCH 16/26] Update update --- .../Models/Api/CharacterTokenResponse.cs | 4 +- .../Models/AuthenticationStuff.cs | 15 +++++ .../OfficialCentralCommandAuthentication.cs | 66 ++++++++++--------- .../Services/ServerAuthenticationService.cs | 2 +- 4 files changed, 53 insertions(+), 34 deletions(-) diff --git a/UnitystationLauncher/Models/Api/CharacterTokenResponse.cs b/UnitystationLauncher/Models/Api/CharacterTokenResponse.cs index fc5d1235..12c6596a 100644 --- a/UnitystationLauncher/Models/Api/CharacterTokenResponse.cs +++ b/UnitystationLauncher/Models/Api/CharacterTokenResponse.cs @@ -2,5 +2,5 @@ public class CharacterTokenResponse : JsonObject { - public string CharacterToken { get; set; } -} \ No newline at end of file + public string token { get; set; } +} \ No newline at end of file diff --git a/UnitystationLauncher/Models/AuthenticationStuff.cs b/UnitystationLauncher/Models/AuthenticationStuff.cs index c0834312..0b58a9ea 100644 --- a/UnitystationLauncher/Models/AuthenticationStuff.cs +++ b/UnitystationLauncher/Models/AuthenticationStuff.cs @@ -46,6 +46,21 @@ public class ForgotPasswordModel : JsonObject public string? Email { get; set; } } +[Serializable] +public class Registersha512token : JsonObject +{ + [JsonProperty("sha512_token")] + public string? sha512_token { get; set; } +} + + +[Serializable] +public class GetCharacterForkToken : JsonObject +{ + [JsonProperty("fork_compatibility")] + public string? fork_compatibility { get; set; } +} + [Serializable] public class AccountRegisterResponse : JsonObject diff --git a/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs b/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs index d3c52dbc..9a2eaa71 100644 --- a/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs +++ b/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs @@ -30,11 +30,17 @@ public class OfficialCentralCommandAuthentication : IAuthProvider public static string Host => ApiUrls.ApiBaseUrlLogin; private static UriBuilder UriBuilder = new(Host); - public static Uri GetUri(string endpoint, string? queries = null) + public static Uri GetUri(string endpoint, string? queries = null, string BeginningOverride = null) { UriBuilder.Path = $"/accounts/{endpoint}"; + if (string.IsNullOrEmpty(BeginningOverride) == false) + { + UriBuilder.Path =BeginningOverride + $"{endpoint}"; + + } + if (string.IsNullOrEmpty(queries) == false) { UriBuilder.Query = queries; @@ -156,24 +162,23 @@ public async Task> SendForgotPasswordEmail(string email) } } - public Task> SendRegisterSharedSecret(string token, string SharedSecret) + public async Task> SendRegisterSharedSecret(string token, string SharedSecret) { try { - // var requestBody = new ForgotPasswordModel - // { - // Email = email, - // }; - // - // var response = await ApiServer.Post(GetUri("reset-password/"), requestBody); - // - // if (!response.IsSuccess) - // { - // throw response.Exception!; - // } - // - // return response; - return null; + var requestBody = new Registersha512token + { + sha512_token = SharedSecret, + }; + + var response = await ApiServer.Post(GetUri("register-SHA512-for-account/"), requestBody, token); + + if (!response.IsSuccess) + { + throw response.Exception!; + } + + return response; } catch (Exception e) { @@ -182,24 +187,23 @@ public Task> SendRegisterSharedSecret(string token, string } } - public Task> GenerateCharacterSheetTokenForFork(string token, string ForkName) + public async Task> GenerateCharacterSheetTokenForFork(string token, string ForkName) { try { - // var requestBody = new ForgotPasswordModel - // { - // Email = email, - // }; - // - // var response = await ApiServer.Post(GetUri("reset-password/"), requestBody); - // - // if (!response.IsSuccess) - // { - // throw response.Exception!; - // } - // - // return response; - return null; + var requestBody = new GetCharacterForkToken + { + fork_compatibility = ForkName, + }; + + var response = await ApiServer.Post(GetUri("GenForkToken/", BeginningOverride: "/characters/"), requestBody, token); + + if (!response.IsSuccess) + { + throw response.Exception!; + } + + return response; } catch (Exception e) { diff --git a/UnitystationLauncher/Services/ServerAuthenticationService.cs b/UnitystationLauncher/Services/ServerAuthenticationService.cs index e02e302a..4d686e6b 100644 --- a/UnitystationLauncher/Services/ServerAuthenticationService.cs +++ b/UnitystationLauncher/Services/ServerAuthenticationService.cs @@ -99,7 +99,7 @@ public async Task> AuthenticateWithServer(string IP, return new Dictionary { - { "-CharacterToken", CharacterToken.CharacterToken}, + { "-CharacterToken", CharacterToken.token}, { "-SharedSecret", base64Secret}, { "-ServerPublicConnectionKey", Info.ServerConnectionPublicKey}, }; From 722048846577081423f5c2abc56dc083903720e0 Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Sun, 10 Aug 2025 13:03:27 +0100 Subject: [PATCH 17/26] Update --- .../ServerConnectionAuthenticationRequest.cs | 13 ++- .../Services/InstallationService.cs | 29 +++--- .../Interface/IInstallationService.cs | 2 +- .../OfficialCentralCommandAuthentication.cs | 2 +- .../Services/ServerAuthenticationService.cs | 92 +++++++++++++------ UnitystationLauncher/StandardModule.cs | 2 + .../UnitystationLauncher.csproj | 1 + .../ViewModels/InstallationViewModel.cs | 2 +- .../ViewModels/ServerViewModel.cs | 2 +- 9 files changed, 92 insertions(+), 53 deletions(-) diff --git a/UnitystationLauncher/Models/Api/ServerConnectionAuthenticationRequest.cs b/UnitystationLauncher/Models/Api/ServerConnectionAuthenticationRequest.cs index 04d97696..ab2842fe 100644 --- a/UnitystationLauncher/Models/Api/ServerConnectionAuthenticationRequest.cs +++ b/UnitystationLauncher/Models/Api/ServerConnectionAuthenticationRequest.cs @@ -2,10 +2,13 @@ public class ServerConnectionAuthenticationRequest { - public string? EncryptedClientFork { get; set; } - public string? EncryptedClientVersion { get; set; } - public string? EncryptedGoodFileVersion { get; set; } - public string? EncryptedSharedSecret { get; set; } + public string? ClientFork { get; set; } + + public string? ClientVersion { get; set; } + + public string? GoodFileVersion { get; set; } + public string? EncryptedSharedSecret{ get; set; } public string? EncryptedAccountID { get; set; } - public string? EncryptedConnectionPublicServerKey { get; set; } + + public string? ConnectionPublicServerKey{ get; set; } } \ No newline at end of file diff --git a/UnitystationLauncher/Services/InstallationService.cs b/UnitystationLauncher/Services/InstallationService.cs index a83a8ef7..61d58ac9 100644 --- a/UnitystationLauncher/Services/InstallationService.cs +++ b/UnitystationLauncher/Services/InstallationService.cs @@ -158,7 +158,7 @@ public List GetInstallations() return (download, string.Empty); } - public (bool, string) StartInstallation(Guid installationId, string? server = null, short? port = null) + public async Task<(bool, string)> StartInstallation(Guid installationId, string? server = null, short? port = null) { _TTSVersionService.StartTTS(); @@ -180,7 +180,7 @@ public List GetInstallations() EnsureExecutableFlagOnUnixSystems(executable); - string arguments = GetArguments(installation, server, port); + string arguments = await GetArguments(installation, server, port); ProcessStartInfo? startInfo = _environmentService.GetGameProcessStartInfo(executable, arguments); if (startInfo == null) @@ -425,33 +425,34 @@ private static (bool, string) CanStartDownload(Download download) return (true, string.Empty); } - private string GetArguments(Installation Installation, string? server, long? port) + private async Task GetArguments(Installation Installation, string? server, long? port) { string arguments = string.Empty; if (string.IsNullOrWhiteSpace(server) == false) { - //TODO Asynchronous!!! - var Arguments = _IServerAuthenticationService.AuthenticateWithServer(server, Installation).Result; - - var AccountID = _authService.AccountLoginResponse.Account.UniqueIdentifier; - var Username = _authService.AccountLoginResponse.Account.Username; - - arguments += $"-AccountID {AccountID}"; - arguments += $"-Username {Username}"; + var Arguments = await _IServerAuthenticationService.AuthenticateWithServer(server, Installation); + + foreach (var Argument in Arguments) { - arguments += $"{Argument.Key} {Argument.Value}"; + arguments += $"{Argument.Key} {Argument.Value} "; } - arguments += $"--server {server}"; + arguments += $"--server {server} "; if (port.HasValue) { - arguments += $" --port {port}"; + arguments += $" --port {port} "; } } + var AccountID = _authService.AccountLoginResponse.Account.UniqueIdentifier; + var Username = _authService.AccountLoginResponse.Account.Username; + arguments += $"-AccountID {AccountID} "; + arguments += $"-Username {Username} "; + var CharacterToken = await _authService.GenerateCharacterSheetTokenForFork(Installation.ForkName); + arguments += $" -CharacterToken {CharacterToken.token} "; return arguments; } diff --git a/UnitystationLauncher/Services/Interface/IInstallationService.cs b/UnitystationLauncher/Services/Interface/IInstallationService.cs index 6f927d67..4469a067 100644 --- a/UnitystationLauncher/Services/Interface/IInstallationService.cs +++ b/UnitystationLauncher/Services/Interface/IInstallationService.cs @@ -47,7 +47,7 @@ public interface IInstallationService /// Server to connect to, either IP or domain /// Port to use, requires server parameter /// Status code for if it was successful, if unsuccessful the string will have the reason - public (bool, string) StartInstallation(Guid installationId, string? server = null, short? port = null); + public Task<(bool, string)> StartInstallation(Guid installationId, string? server = null, short? port = null); /// /// Deletes an installation diff --git a/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs b/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs index 9a2eaa71..dde0b415 100644 --- a/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs +++ b/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs @@ -196,7 +196,7 @@ public async Task> GenerateCharacterSheetToken fork_compatibility = ForkName, }; - var response = await ApiServer.Post(GetUri("GenForkToken/", BeginningOverride: "/characters/"), requestBody, token); + var response = await ApiServer.Post(GetUri("GenForkToken", BeginningOverride: "/persistence/characters/"), requestBody, token); if (!response.IsSuccess) { diff --git a/UnitystationLauncher/Services/ServerAuthenticationService.cs b/UnitystationLauncher/Services/ServerAuthenticationService.cs index 4d686e6b..14b5c8a0 100644 --- a/UnitystationLauncher/Services/ServerAuthenticationService.cs +++ b/UnitystationLauncher/Services/ServerAuthenticationService.cs @@ -1,11 +1,20 @@ using System; using System.Collections.Generic; +using System.IO; using System.Net.Http; using System.Security.Authentication; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; +using System.Xml; using Newtonsoft.Json; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Encodings; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.OpenSsl; using UnitystationLauncher.Models; using UnitystationLauncher.Models.Api; @@ -16,12 +25,10 @@ public interface IServerAuthenticationService public Task GetServerInfoByIP(string IP); public Task> AuthenticateWithServer(string IP, Installation Installation); - } public class ServerAuthenticationService : IServerAuthenticationService { - public ServerAuthenticationService(AuthService AuthService) { _AuthService = AuthService; @@ -35,27 +42,45 @@ public ServerAuthenticationService(AuthService AuthService) public async Task GetServerInfoByIP(string IP) { - string Port = "7778"; - string url = $"http://{IP}:{Port}/"; + try + { + string Port = "7778"; + string url = $"http://{IP}:{Port}/"; - var response = await _httpClient.GetAsync(url); - response.EnsureSuccessStatusCode(); + var response = await _httpClient.GetAsync(url); + response.EnsureSuccessStatusCode(); - string content = await response.Content.ReadAsStringAsync(); - var serverInfo = JsonConvert.DeserializeObject(content); + string content = await response.Content.ReadAsStringAsync(); + var serverInfo = JsonConvert.DeserializeObject(content); - return serverInfo; + return serverInfo; + } + catch (Exception ex) + { + return null; + } } - public async Task> AuthenticateWithServer(string IP, Installation Installation) + + static AsymmetricKeyParameter ImportKeyFromPem(string pem) { + using (var sr = new StringReader(pem)) + { + var pr = new PemReader(sr); + return (AsymmetricKeyParameter) pr.ReadObject(); + } + } + + public async Task> AuthenticateWithServer(string IP, Installation Installation) + { var Info = await GetServerInfoByIP(IP); - string base64PublicKey = Info.ServerPublicKey; - byte[] publicKeyBytes = Convert.FromBase64String(base64PublicKey); + var RSAEncrypt = new OaepEncoding( + new RsaEngine(), + new Sha256Digest() + ); - using RSA rsa = RSA.Create(); - rsa.ImportRSAPublicKey(publicKeyBytes, out _); + RSAEncrypt.Init(true, ImportKeyFromPem(Info.ServerPublicKey)); // false = decrypt mode byte[] sharedSecret = new byte[32]; // 256-bit key RandomNumberGenerator.Fill(sharedSecret); @@ -63,14 +88,26 @@ public async Task> AuthenticateWithServer(string IP, // Optional: convert to Base64 if you want to transmit/store it string base64Secret = Convert.ToBase64String(sharedSecret); + var SHA512Check = Convert.ToBase64String( + SHA512.ComputeHash( + Encoding.UTF8.GetBytes( + base64Secret + + Info.ServerPublicKey + + Installation.BuildVersion.ToString() + + Installation.ForkName.ToString() + + Installation.GoodFileVersion.ToString() + + Info.ServerConnectionPublicKey))); + _AuthService.RegisterJoiningServerWithSecret(SHA512Check); + + var ToSend = new ServerConnectionAuthenticationRequest { - EncryptedConnectionPublicServerKey = EncryptString(rsa, Info.ServerConnectionPublicKey), - EncryptedClientVersion = EncryptString(rsa, Installation.BuildVersion.ToString()), - EncryptedGoodFileVersion = EncryptString(rsa, Installation.GoodFileVersion.ToString()), - EncryptedClientFork = EncryptString(rsa, Installation.ForkName.ToString()), - EncryptedSharedSecret = EncryptString(rsa, base64Secret), - EncryptedAccountID = EncryptString(rsa, _AuthService.AccountLoginResponse.Account.UniqueIdentifier) + ConnectionPublicServerKey = Info.ServerConnectionPublicKey, + ClientVersion = Installation.BuildVersion.ToString(), + GoodFileVersion = Installation.GoodFileVersion.ToString(), + ClientFork = Installation.ForkName.ToString(), + EncryptedSharedSecret = EncryptString(RSAEncrypt, base64Secret), + EncryptedAccountID = EncryptString(RSAEncrypt, _AuthService.AccountLoginResponse.Account.UniqueIdentifier) }; @@ -92,21 +129,16 @@ public async Task> AuthenticateWithServer(string IP, throw new AuthenticationException(contentBack + $" When trying to authenticate with {url}"); } - var SHA512Check = Convert.ToBase64String(SHA512.ComputeHash(Encoding.UTF8.GetBytes(base64Secret + Info.ServerPublicKey))); - _AuthService.RegisterJoiningServerWithSecret(SHA512Check); - - var CharacterToken = await _AuthService.GenerateCharacterSheetTokenForFork(Installation.ForkName); - return new Dictionary { - { "-CharacterToken", CharacterToken.token}, - { "-SharedSecret", base64Secret}, - { "-ServerPublicConnectionKey", Info.ServerConnectionPublicKey}, + {"-SharedSecret", base64Secret}, + {"-ServerPublicConnectionKey", Info.ServerConnectionPublicKey}, }; } - private string EncryptString(RSA rsa, string ToEncrypt) + private string EncryptString(OaepEncoding rsa, string ToEncrypt) { - return Convert.ToBase64String(rsa.Encrypt(Encoding.UTF8.GetBytes(ToEncrypt), RSAEncryptionPadding.OaepSHA256)); + var Bytes = Encoding.UTF8.GetBytes(ToEncrypt); + return Convert.ToBase64String(rsa.ProcessBlock(Bytes, 0, Bytes.Length)); } } \ No newline at end of file diff --git a/UnitystationLauncher/StandardModule.cs b/UnitystationLauncher/StandardModule.cs index 9029474e..ca8fd332 100644 --- a/UnitystationLauncher/StandardModule.cs +++ b/UnitystationLauncher/StandardModule.cs @@ -26,6 +26,8 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + // View Models builder.RegisterAssemblyTypes(ThisAssembly) .Where(t => t.Name.EndsWith("ViewModel")); diff --git a/UnitystationLauncher/UnitystationLauncher.csproj b/UnitystationLauncher/UnitystationLauncher.csproj index 71281178..ef6f0205 100644 --- a/UnitystationLauncher/UnitystationLauncher.csproj +++ b/UnitystationLauncher/UnitystationLauncher.csproj @@ -72,6 +72,7 @@ + diff --git a/UnitystationLauncher/ViewModels/InstallationViewModel.cs b/UnitystationLauncher/ViewModels/InstallationViewModel.cs index 8a07b9d2..f1c056ae 100644 --- a/UnitystationLauncher/ViewModels/InstallationViewModel.cs +++ b/UnitystationLauncher/ViewModels/InstallationViewModel.cs @@ -30,7 +30,7 @@ public InstallationViewModel(Installation installation, IInstallationService ins private void LaunchInstallation() { - _installationService.StartInstallation(Installation.InstallationId); + _ = _installationService.StartInstallation(Installation.InstallationId); } private void DeleteInstallation() diff --git a/UnitystationLauncher/ViewModels/ServerViewModel.cs b/UnitystationLauncher/ViewModels/ServerViewModel.cs index d553cdee..b943003e 100644 --- a/UnitystationLauncher/ViewModels/ServerViewModel.cs +++ b/UnitystationLauncher/ViewModels/ServerViewModel.cs @@ -56,7 +56,7 @@ public void LaunchGame() return; } - _installationService.StartInstallation(Installation.InstallationId, Server.ServerIp, (short)Server.ServerPort); + _ = _installationService.StartInstallation(Installation.InstallationId, Server.ServerIp, (short)Server.ServerPort); } private async Task GetPing(IScheduler _, CancellationToken cancellationToken) From d70bae7abe8b3b156b2da7c5a74e619c28482795 Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Sun, 10 Aug 2025 15:55:24 +0100 Subject: [PATCH 18/26] Changes --- .../PipeHubBuildCommunication.cs | 2 +- .../Models/Api/CharacterTokenResponse.cs | 2 +- .../ServerConnectionAuthenticationRequest.cs | 10 +++++----- UnitystationLauncher/Services/ApiServer.cs | 1 - .../Services/InstallationService.cs | 20 +++++++++---------- .../OfficialCentralCommandAuthentication.cs | 16 +++++++-------- .../Services/ServerAuthenticationService.cs | 2 +- UnitystationLauncher/StandardModule.cs | 2 +- .../ViewModels/InstallationViewModel.cs | 2 +- 9 files changed, 28 insertions(+), 29 deletions(-) diff --git a/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs b/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs index b95a9a34..983afc50 100644 --- a/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs +++ b/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs @@ -30,7 +30,7 @@ public PipeHubBuildCommunication() PipeTransmissionMode.Byte, PipeOptions.Asynchronous); } - public async Task StartServerPipe() + public static async Task StartServerPipe() { if (_serverPipe == null) { diff --git a/UnitystationLauncher/Models/Api/CharacterTokenResponse.cs b/UnitystationLauncher/Models/Api/CharacterTokenResponse.cs index 12c6596a..32b9e53f 100644 --- a/UnitystationLauncher/Models/Api/CharacterTokenResponse.cs +++ b/UnitystationLauncher/Models/Api/CharacterTokenResponse.cs @@ -3,4 +3,4 @@ public class CharacterTokenResponse : JsonObject { public string token { get; set; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/UnitystationLauncher/Models/Api/ServerConnectionAuthenticationRequest.cs b/UnitystationLauncher/Models/Api/ServerConnectionAuthenticationRequest.cs index ab2842fe..ae223b32 100644 --- a/UnitystationLauncher/Models/Api/ServerConnectionAuthenticationRequest.cs +++ b/UnitystationLauncher/Models/Api/ServerConnectionAuthenticationRequest.cs @@ -3,12 +3,12 @@ public class ServerConnectionAuthenticationRequest { public string? ClientFork { get; set; } - + public string? ClientVersion { get; set; } - + public string? GoodFileVersion { get; set; } - public string? EncryptedSharedSecret{ get; set; } + public string? EncryptedSharedSecret { get; set; } public string? EncryptedAccountID { get; set; } - - public string? ConnectionPublicServerKey{ get; set; } + + public string? ConnectionPublicServerKey { get; set; } } \ No newline at end of file diff --git a/UnitystationLauncher/Services/ApiServer.cs b/UnitystationLauncher/Services/ApiServer.cs index 1a59f592..db51e2c2 100644 --- a/UnitystationLauncher/Services/ApiServer.cs +++ b/UnitystationLauncher/Services/ApiServer.cs @@ -67,7 +67,6 @@ internal static async Task> Put(Uri uri, JsonObject body, string var sss = JsonConvert.SerializeObject(body); request.Content = new StringContent(sss, Encoding.UTF8, "application/json"); - //request.Content = body.ToStringContent(); return await Send(request); } diff --git a/UnitystationLauncher/Services/InstallationService.cs b/UnitystationLauncher/Services/InstallationService.cs index 61d58ac9..5234c1b9 100644 --- a/UnitystationLauncher/Services/InstallationService.cs +++ b/UnitystationLauncher/Services/InstallationService.cs @@ -427,33 +427,33 @@ private static (bool, string) CanStartDownload(Download download) private async Task GetArguments(Installation Installation, string? server, long? port) { - string arguments = string.Empty; + StringBuilder arguments = new StringBuilder(); if (string.IsNullOrWhiteSpace(server) == false) { var Arguments = await _IServerAuthenticationService.AuthenticateWithServer(server, Installation); - - + + foreach (var Argument in Arguments) { - arguments += $"{Argument.Key} {Argument.Value} "; + arguments.Append($"{Argument.Key} {Argument.Value} "); } - arguments += $"--server {server} "; + arguments.Append($"--server {server} "); if (port.HasValue) { - arguments += $" --port {port} "; + arguments.Append($"--port {port} "); } } var AccountID = _authService.AccountLoginResponse.Account.UniqueIdentifier; var Username = _authService.AccountLoginResponse.Account.Username; - arguments += $"-AccountID {AccountID} "; - arguments += $"-Username {Username} "; + arguments.Append($"-AccountID {AccountID} "); + arguments.Append($"-Username {Username} "); var CharacterToken = await _authService.GenerateCharacterSheetTokenForFork(Installation.ForkName); - arguments += $" -CharacterToken {CharacterToken.token} "; - return arguments; + arguments.Append($"-CharacterToken {CharacterToken.token} "); + return arguments.ToString(); } diff --git a/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs b/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs index dde0b415..25389125 100644 --- a/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs +++ b/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs @@ -37,10 +37,10 @@ public static Uri GetUri(string endpoint, string? queries = null, string Beginni if (string.IsNullOrEmpty(BeginningOverride) == false) { - UriBuilder.Path =BeginningOverride + $"{endpoint}"; + UriBuilder.Path = BeginningOverride + $"{endpoint}"; } - + if (string.IsNullOrEmpty(queries) == false) { UriBuilder.Query = queries; @@ -170,14 +170,14 @@ public async Task> SendRegisterSharedSecret(string token, { sha512_token = SharedSecret, }; - + var response = await ApiServer.Post(GetUri("register-SHA512-for-account/"), requestBody, token); - + if (!response.IsSuccess) { throw response.Exception!; } - + return response; } catch (Exception e) @@ -195,14 +195,14 @@ public async Task> GenerateCharacterSheetToken { fork_compatibility = ForkName, }; - + var response = await ApiServer.Post(GetUri("GenForkToken", BeginningOverride: "/persistence/characters/"), requestBody, token); - + if (!response.IsSuccess) { throw response.Exception!; } - + return response; } catch (Exception e) diff --git a/UnitystationLauncher/Services/ServerAuthenticationService.cs b/UnitystationLauncher/Services/ServerAuthenticationService.cs index 14b5c8a0..c0943f70 100644 --- a/UnitystationLauncher/Services/ServerAuthenticationService.cs +++ b/UnitystationLauncher/Services/ServerAuthenticationService.cs @@ -67,7 +67,7 @@ static AsymmetricKeyParameter ImportKeyFromPem(string pem) using (var sr = new StringReader(pem)) { var pr = new PemReader(sr); - return (AsymmetricKeyParameter) pr.ReadObject(); + return (AsymmetricKeyParameter)pr.ReadObject(); } } diff --git a/UnitystationLauncher/StandardModule.cs b/UnitystationLauncher/StandardModule.cs index ca8fd332..e09df364 100644 --- a/UnitystationLauncher/StandardModule.cs +++ b/UnitystationLauncher/StandardModule.cs @@ -27,7 +27,7 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); - + // View Models builder.RegisterAssemblyTypes(ThisAssembly) .Where(t => t.Name.EndsWith("ViewModel")); diff --git a/UnitystationLauncher/ViewModels/InstallationViewModel.cs b/UnitystationLauncher/ViewModels/InstallationViewModel.cs index f1c056ae..6a75d361 100644 --- a/UnitystationLauncher/ViewModels/InstallationViewModel.cs +++ b/UnitystationLauncher/ViewModels/InstallationViewModel.cs @@ -30,7 +30,7 @@ public InstallationViewModel(Installation installation, IInstallationService ins private void LaunchInstallation() { - _ = _installationService.StartInstallation(Installation.InstallationId); + _ = _installationService.StartInstallation(Installation.InstallationId); } private void DeleteInstallation() From 70266b356f23e09a2a131b37bf7bba02c4d8aadb Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Sun, 10 Aug 2025 15:56:55 +0100 Subject: [PATCH 19/26] a --- UnitystationLauncher/Services/GameCommunicationPipeService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnitystationLauncher/Services/GameCommunicationPipeService.cs b/UnitystationLauncher/Services/GameCommunicationPipeService.cs index 80695161..c30585c1 100644 --- a/UnitystationLauncher/Services/GameCommunicationPipeService.cs +++ b/UnitystationLauncher/Services/GameCommunicationPipeService.cs @@ -10,7 +10,7 @@ public class GameCommunicationPipeService : IGameCommunicationPipeService public void Init() { PipeHubBuildCommunication data = new(); - _ = data.StartServerPipe(); + _ = PipeHubBuildCommunication.StartServerPipe(); _coolPipeHubBuildCommunication = data; } } \ No newline at end of file From e8bb901091aa9337910264428455683bced1b088 Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Sun, 10 Aug 2025 16:09:13 +0100 Subject: [PATCH 20/26] fix --- .../InstallationService/MockNoActiveDownloads.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnitystationLauncher.Tests/MocksRepository/InstallationService/MockNoActiveDownloads.cs b/UnitystationLauncher.Tests/MocksRepository/InstallationService/MockNoActiveDownloads.cs index 8bc1b538..588a3685 100644 --- a/UnitystationLauncher.Tests/MocksRepository/InstallationService/MockNoActiveDownloads.cs +++ b/UnitystationLauncher.Tests/MocksRepository/InstallationService/MockNoActiveDownloads.cs @@ -28,7 +28,7 @@ public List GetInstallations() throw new NotImplementedException(); } - public (bool, string) StartInstallation(Guid installationId, string? server = null, short? port = null) + public Task<(bool, string)> StartInstallation(Guid installationId, string? server = null, short? port = null) { throw new NotImplementedException(); } From f9b7663aaa8a95cfe1b6a2f2c7d8dfc5726663c9 Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Sun, 10 Aug 2025 21:29:53 +0100 Subject: [PATCH 21/26] Version --- .../Assets/org.unitystation.StationHub.metainfo.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/UnitystationLauncher/Assets/org.unitystation.StationHub.metainfo.xml b/UnitystationLauncher/Assets/org.unitystation.StationHub.metainfo.xml index f5ae82a9..9dedfc9a 100644 --- a/UnitystationLauncher/Assets/org.unitystation.StationHub.metainfo.xml +++ b/UnitystationLauncher/Assets/org.unitystation.StationHub.metainfo.xml @@ -65,6 +65,15 @@ + + +

      New:

      +
        +
      • login is now in the hub
      • +
      • Hub now supports a new authentication flow
      • +
      +
      +

      Fix:

      From fea6147c719f52074b2658091ea20b0dc70aba17 Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Sun, 10 Aug 2025 21:30:11 +0100 Subject: [PATCH 22/26] Bump --- UnitystationLauncher/Constants/AppInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnitystationLauncher/Constants/AppInfo.cs b/UnitystationLauncher/Constants/AppInfo.cs index f2bafd01..a6f9048c 100644 --- a/UnitystationLauncher/Constants/AppInfo.cs +++ b/UnitystationLauncher/Constants/AppInfo.cs @@ -4,5 +4,5 @@ public static class AppInfo { // Whenever you change the currentBuild here, please also update the one in // UnitystationLauncher/Assets/org.unitystation.StationHub.metainfo.xml - public const int CurrentBuild = 938; + public const int CurrentBuild = 939; } \ No newline at end of file From 266f40627fa6578ac6883d4cf1f1293729b621ee Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Sun, 10 Aug 2025 22:36:05 +0100 Subject: [PATCH 23/26] Coda stuff --- .../PipeHubBuildCommunication.cs | 7 ++++--- .../Services/OfficialCentralCommandAuthentication.cs | 12 ++++++------ UnitystationLauncher/ViewModels/LoginViewModel.cs | 4 ++-- .../ViewModels/MainWindowViewModel.cs | 1 + 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs b/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs index 983afc50..078516be 100644 --- a/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs +++ b/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs @@ -17,6 +17,7 @@ public class PipeHubBuildCommunication : IDisposable private static StreamReader? _reader; private static StreamWriter? _writer; + private const string Unitystation_Hub_Build_Communication = "Unitystation_Hub_Build_Communication"; public PipeHubBuildCommunication() { _serverPipe?.Close(); @@ -26,7 +27,7 @@ public PipeHubBuildCommunication() _writer?.Close(); _writer?.Dispose(); - _serverPipe = new("Unitystation_Hub_Build_Communication", PipeDirection.InOut, 1, + _serverPipe = new(Unitystation_Hub_Build_Communication, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); } @@ -34,7 +35,7 @@ public static async Task StartServerPipe() { if (_serverPipe == null) { - _serverPipe = new("Unitystation_Hub_Build_Communication", PipeDirection.InOut, 1, + _serverPipe = new(Unitystation_Hub_Build_Communication, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); } @@ -55,7 +56,7 @@ public static async Task StartServerPipe() { Log.Error(e.ToString()); _serverPipe.Close(); - _serverPipe = new("Unitystation_Hub_Build_Communication", PipeDirection.InOut, + _serverPipe = new(Unitystation_Hub_Build_Communication, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); await _serverPipe.WaitForConnectionAsync(); diff --git a/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs b/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs index 25389125..c056aef3 100644 --- a/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs +++ b/UnitystationLauncher/Services/OfficialCentralCommandAuthentication.cs @@ -58,7 +58,7 @@ public async Task> Login(string token) ApiResult response = await ApiServer.Post(GetUri("login-token"), requestBody); - if (!response.IsSuccess) + if (response.IsSuccess == false) { throw response.Exception!; } @@ -76,7 +76,7 @@ public static async Task> Login(string emailAddr ApiResult response = await ApiServer.Post(GetUri("login-credentials"), requestBody); - if (!response.IsSuccess) + if (response.IsSuccess == false) { throw response.Exception!; } @@ -128,7 +128,7 @@ public async Task> Register( var response = await ApiServer.Post(GetUri("register"), requestBody); - if (!response.IsSuccess) + if (response.IsSuccess == false) { throw response.Exception!; } @@ -148,7 +148,7 @@ public async Task> SendForgotPasswordEmail(string email) var response = await ApiServer.Post(GetUri("reset-password/"), requestBody); - if (!response.IsSuccess) + if (response.IsSuccess == false) { throw response.Exception!; } @@ -173,7 +173,7 @@ public async Task> SendRegisterSharedSecret(string token, var response = await ApiServer.Post(GetUri("register-SHA512-for-account/"), requestBody, token); - if (!response.IsSuccess) + if (response.IsSuccess == false) { throw response.Exception!; } @@ -198,7 +198,7 @@ public async Task> GenerateCharacterSheetToken var response = await ApiServer.Post(GetUri("GenForkToken", BeginningOverride: "/persistence/characters/"), requestBody, token); - if (!response.IsSuccess) + if (response.IsSuccess == false) { throw response.Exception!; } diff --git a/UnitystationLauncher/ViewModels/LoginViewModel.cs b/UnitystationLauncher/ViewModels/LoginViewModel.cs index 1582171e..f4312261 100644 --- a/UnitystationLauncher/ViewModels/LoginViewModel.cs +++ b/UnitystationLauncher/ViewModels/LoginViewModel.cs @@ -35,7 +35,7 @@ public LoginViewModel( !string.IsNullOrWhiteSpace(u) && !string.IsNullOrWhiteSpace(p)); - Login = ReactiveCommand.CreateFromTask( + Login = ReactiveCommand.Create( UserLoginAsync, possibleCredentials); @@ -62,7 +62,7 @@ public string Password public ReactiveCommand Create { get; } public ReactiveCommand ForgotPw { get; } - public async Task UserLoginAsync() + public LoginStatusViewModel UserLoginAsync() { _authService.LoginMsg = new LoginMsg { diff --git a/UnitystationLauncher/ViewModels/MainWindowViewModel.cs b/UnitystationLauncher/ViewModels/MainWindowViewModel.cs index bc801f0b..2eb16d4c 100644 --- a/UnitystationLauncher/ViewModels/MainWindowViewModel.cs +++ b/UnitystationLauncher/ViewModels/MainWindowViewModel.cs @@ -81,6 +81,7 @@ async Task CheckForExistingUserAsync() Content = _loginStatusVm.Value; await AttemptAuthRefreshAsync(); } + //Will go to login screen if null } async Task AttemptAuthRefreshAsync() From 776c4644b6205d79833274ba79e38502a9c257da Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Sun, 10 Aug 2025 22:37:21 +0100 Subject: [PATCH 24/26] null --- .../Models/AuthenticationStuff.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/UnitystationLauncher/Models/AuthenticationStuff.cs b/UnitystationLauncher/Models/AuthenticationStuff.cs index 0b58a9ea..47730cfb 100644 --- a/UnitystationLauncher/Models/AuthenticationStuff.cs +++ b/UnitystationLauncher/Models/AuthenticationStuff.cs @@ -85,8 +85,7 @@ public class AccountRegisterDetails : JsonObject [Serializable] public class AccountLoginResponse : JsonObject { - [JsonProperty("token")] - public string Token { get; set; } + [JsonProperty("token")] public string Token { get; set; } = ""; [JsonProperty("account")] public AccountGetResponse Account { get; set; } @@ -96,10 +95,10 @@ public class AccountLoginResponse : JsonObject public class AccountGetResponse : JsonObject { [JsonProperty("unique_identifier")] - public string UniqueIdentifier { get; set; } + public string UniqueIdentifier { get; set; } = ""; [JsonProperty("username")] - public string Username { get; set; } + public string Username { get; set; } = ""; [JsonProperty("is_verified")] public bool IsVerified { get; set; } @@ -108,30 +107,29 @@ public class AccountGetResponse : JsonObject [Serializable] public class AccountLoginToken : JsonObject, ITokenAuthable { - public string Token { get; set; } + public string Token { get; set; } = ""; } [Serializable] public class AccountLogout : JsonObject, ITokenAuthable { - public string Token { get; set; } + public string Token { get; set; } = ""; } [Serializable] public class AccountResendEmailConfirmationRequest : JsonObject { [JsonProperty("email")] - public string Email { get; set; } + public string Email { get; set; } = ""; } [Serializable] public class AccountLoginCredentials : JsonObject { [JsonProperty("email")] - public string Email { get; set; } + public string Email { get; set; } = ""; - [JsonProperty("password")] - public string Password { get; set; } + [JsonProperty("password")] public string Password { get; set; } = ""; } public class ApiResult : JsonObject where T : JsonObject From f00d61a438af3b73e24c1381ba2fcb5caa61eeed Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Sun, 10 Aug 2025 22:38:56 +0100 Subject: [PATCH 25/26] log --- UnitystationLauncher/Services/AuthService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/UnitystationLauncher/Services/AuthService.cs b/UnitystationLauncher/Services/AuthService.cs index f3aa88b1..388590e1 100644 --- a/UnitystationLauncher/Services/AuthService.cs +++ b/UnitystationLauncher/Services/AuthService.cs @@ -49,8 +49,9 @@ private void LoadAuthSettings() AccountLoginResponse = AccountLoginResponseA; } } - catch (Exception) + catch (Exception ex) { + Log.Error(ex.ToString()); // Something went wrong reading the auth settings. Just ask the user to log in again. // The auth settings file will get overwritten after they do so we don't need to clean it up. } From 00ca55c3a9342f0c26d469d872eb649dd1d237a0 Mon Sep 17 00:00:00 2001 From: Bod9001 Date: Fri, 26 Sep 2025 16:34:42 +0100 Subject: [PATCH 26/26] ServerConnectionNegotiationPort --- UnitystationLauncher/Models/Api/Server.cs | 4 +++- .../Services/InstallationService.cs | 8 ++++---- .../Services/Interface/IInstallationService.cs | 2 +- .../Services/ServerAuthenticationService.cs | 14 ++++++-------- UnitystationLauncher/ViewModels/ServerViewModel.cs | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/UnitystationLauncher/Models/Api/Server.cs b/UnitystationLauncher/Models/Api/Server.cs index 206b4f3b..92ce41e4 100644 --- a/UnitystationLauncher/Models/Api/Server.cs +++ b/UnitystationLauncher/Models/Api/Server.cs @@ -8,12 +8,13 @@ namespace UnitystationLauncher.Models.Api [Serializable] public class Server { - public Server(string forkName, int buildVersion, string serverIp, int serverPort) + public Server(string forkName, int buildVersion, string serverIp, int serverPort, int ServerConnectionNegotiationPort) { ForkName = forkName; BuildVersion = buildVersion; ServerIp = serverIp; ServerPort = serverPort; + this.ServerConnectionNegotiationPort = ServerConnectionNegotiationPort; } public string? ServerName { get; set; } @@ -26,6 +27,7 @@ public Server(string forkName, int buildVersion, string serverIp, int serverPort public int PlayerCountMax { get; set; } public string ServerIp { get; } public int ServerPort { get; } + public int ServerConnectionNegotiationPort { get; } public string? WinDownload { get; set; } public string? OsxDownload { get; set; } public string? LinuxDownload { get; set; } diff --git a/UnitystationLauncher/Services/InstallationService.cs b/UnitystationLauncher/Services/InstallationService.cs index 5234c1b9..4fea3d66 100644 --- a/UnitystationLauncher/Services/InstallationService.cs +++ b/UnitystationLauncher/Services/InstallationService.cs @@ -158,7 +158,7 @@ public List GetInstallations() return (download, string.Empty); } - public async Task<(bool, string)> StartInstallation(Guid installationId, string? server = null, short? port = null) + public async Task<(bool, string)> StartInstallation(Guid installationId, string? server = null, short? NegotiationPort = null , short? port = null) { _TTSVersionService.StartTTS(); @@ -180,7 +180,7 @@ public List GetInstallations() EnsureExecutableFlagOnUnixSystems(executable); - string arguments = await GetArguments(installation, server, port); + string arguments = await GetArguments(installation, server, NegotiationPort, port); ProcessStartInfo? startInfo = _environmentService.GetGameProcessStartInfo(executable, arguments); if (startInfo == null) @@ -425,13 +425,13 @@ private static (bool, string) CanStartDownload(Download download) return (true, string.Empty); } - private async Task GetArguments(Installation Installation, string? server, long? port) + private async Task GetArguments(Installation Installation, string? server, int? NegotiationPort, long? port) { StringBuilder arguments = new StringBuilder(); if (string.IsNullOrWhiteSpace(server) == false) { - var Arguments = await _IServerAuthenticationService.AuthenticateWithServer(server, Installation); + var Arguments = await _IServerAuthenticationService.AuthenticateWithServer(server, NegotiationPort.Value, Installation); foreach (var Argument in Arguments) diff --git a/UnitystationLauncher/Services/Interface/IInstallationService.cs b/UnitystationLauncher/Services/Interface/IInstallationService.cs index 4469a067..7ee39372 100644 --- a/UnitystationLauncher/Services/Interface/IInstallationService.cs +++ b/UnitystationLauncher/Services/Interface/IInstallationService.cs @@ -47,7 +47,7 @@ public interface IInstallationService /// Server to connect to, either IP or domain /// Port to use, requires server parameter /// Status code for if it was successful, if unsuccessful the string will have the reason - public Task<(bool, string)> StartInstallation(Guid installationId, string? server = null, short? port = null); + public Task<(bool, string)> StartInstallation(Guid installationId, string? server = null, short? NegotiationPort = null , short? port = null); /// /// Deletes an installation diff --git a/UnitystationLauncher/Services/ServerAuthenticationService.cs b/UnitystationLauncher/Services/ServerAuthenticationService.cs index c0943f70..978e62bc 100644 --- a/UnitystationLauncher/Services/ServerAuthenticationService.cs +++ b/UnitystationLauncher/Services/ServerAuthenticationService.cs @@ -22,9 +22,9 @@ namespace UnitystationLauncher.Services; public interface IServerAuthenticationService { - public Task GetServerInfoByIP(string IP); + public Task GetServerInfoByIP(string IP, int Port); - public Task> AuthenticateWithServer(string IP, Installation Installation); + public Task> AuthenticateWithServer(string IP, int Port, Installation Installation); } public class ServerAuthenticationService : IServerAuthenticationService @@ -40,11 +40,10 @@ public ServerAuthenticationService(AuthService AuthService) private readonly SHA512 SHA512 = SHA512.Create(); - public async Task GetServerInfoByIP(string IP) + public async Task GetServerInfoByIP(string IP, int Port) { try { - string Port = "7778"; string url = $"http://{IP}:{Port}/"; var response = await _httpClient.GetAsync(url); @@ -72,9 +71,9 @@ static AsymmetricKeyParameter ImportKeyFromPem(string pem) } - public async Task> AuthenticateWithServer(string IP, Installation Installation) + public async Task> AuthenticateWithServer(string IP, int Port, Installation Installation) { - var Info = await GetServerInfoByIP(IP); + var Info = await GetServerInfoByIP(IP, Port); var RSAEncrypt = new OaepEncoding( new RsaEngine(), new Sha256Digest() @@ -116,8 +115,7 @@ public async Task> AuthenticateWithServer(string IP, // Wrap it in a StringContent with JSON media type var content = new StringContent(json, Encoding.UTF8, "application/json"); - - string Port = "7778"; + string url = $"http://{IP}:{Port}/"; var response = await _httpClient.PostAsync(url, content); diff --git a/UnitystationLauncher/ViewModels/ServerViewModel.cs b/UnitystationLauncher/ViewModels/ServerViewModel.cs index b943003e..13b10a39 100644 --- a/UnitystationLauncher/ViewModels/ServerViewModel.cs +++ b/UnitystationLauncher/ViewModels/ServerViewModel.cs @@ -56,7 +56,7 @@ public void LaunchGame() return; } - _ = _installationService.StartInstallation(Installation.InstallationId, Server.ServerIp, (short)Server.ServerPort); + _ = _installationService.StartInstallation(Installation.InstallationId, Server.ServerIp, (short)Server.ServerConnectionNegotiationPort, (short)Server.ServerPort); } private async Task GetPing(IScheduler _, CancellationToken cancellationToken)