From c7ae55a4082d7d5f0a5b9832ef6d78807cec33bd Mon Sep 17 00:00:00 2001 From: Jasper Middendorp Date: Sat, 7 Mar 2026 02:44:11 +0100 Subject: [PATCH 1/2] Add quick access panel (Cmd+Shift+K), app icon, and App Store fixes - Global hotkey (Cmd+Shift+K) opens a floating panel near cursor for quick secret search, Touch ID, and copy to clipboard - Auto-clears clipboard after 30s, smart extraction for structured types (password from login, primary from api_key) - App icon: hexagonal key in NoBoxDev style (all macOS sizes) - Fix App Store validation: LSApplicationCategoryType in Info.plist, app sandbox enabled with network entitlements Co-Authored-By: Claude Opus 4.6 --- Info.plist | 6 +- .../contents.xcworkspacedata | 7 - .../xcschemes/xcschememanagement.plist | 14 - .../AppIcon.appiconset/Contents.json | 10 + .../AppIcon.appiconset/icon_128x128.png | Bin 0 -> 3015 bytes .../AppIcon.appiconset/icon_128x128@2x.png | Bin 0 -> 6210 bytes .../AppIcon.appiconset/icon_16x16.png | Bin 0 -> 371 bytes .../AppIcon.appiconset/icon_16x16@2x.png | Bin 0 -> 694 bytes .../AppIcon.appiconset/icon_256x256.png | Bin 0 -> 6210 bytes .../AppIcon.appiconset/icon_256x256@2x.png | Bin 0 -> 11650 bytes .../AppIcon.appiconset/icon_32x32.png | Bin 0 -> 694 bytes .../AppIcon.appiconset/icon_32x32@2x.png | Bin 0 -> 1432 bytes .../AppIcon.appiconset/icon_512x512.png | Bin 0 -> 11650 bytes .../AppIcon.appiconset/icon_512x512@2x.png | Bin 0 -> 27630 bytes NBox/HotkeyManager.swift | 55 ++++ NBox/NBox.entitlements | 6 +- NBox/NBoxApp.swift | 8 + NBox/QuickAccessPanel.swift | 249 ++++++++++++++++++ .../project.pbxproj | 32 ++- 19 files changed, 349 insertions(+), 38 deletions(-) delete mode 100644 NBox.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 NBox.xcodeproj/xcuserdata/jaspermiddendorp.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 NBox/Assets.xcassets/AppIcon.appiconset/icon_128x128.png create mode 100644 NBox/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png create mode 100644 NBox/Assets.xcassets/AppIcon.appiconset/icon_16x16.png create mode 100644 NBox/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png create mode 100644 NBox/Assets.xcassets/AppIcon.appiconset/icon_256x256.png create mode 100644 NBox/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png create mode 100644 NBox/Assets.xcassets/AppIcon.appiconset/icon_32x32.png create mode 100644 NBox/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png create mode 100644 NBox/Assets.xcassets/AppIcon.appiconset/icon_512x512.png create mode 100644 NBox/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png create mode 100644 NBox/HotkeyManager.swift create mode 100644 NBox/QuickAccessPanel.swift rename {NBox.xcodeproj => Noxkey.xcodeproj}/project.pbxproj (89%) diff --git a/Info.plist b/Info.plist index af1e52e..b9e4465 100644 --- a/Info.plist +++ b/Info.plist @@ -2,9 +2,7 @@ - LSUIElement - - NSFaceIDUsageDescription - nbox uses Touch ID to protect your secrets. + LSApplicationCategoryType + public.app-category.utilities diff --git a/NBox.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/NBox.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/NBox.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/NBox.xcodeproj/xcuserdata/jaspermiddendorp.xcuserdatad/xcschemes/xcschememanagement.plist b/NBox.xcodeproj/xcuserdata/jaspermiddendorp.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 092d39c..0000000 --- a/NBox.xcodeproj/xcuserdata/jaspermiddendorp.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,14 +0,0 @@ - - - - - SchemeUserState - - NBox.xcscheme_^#shared#^_ - - orderHint - 0 - - - - diff --git a/NBox/Assets.xcassets/AppIcon.appiconset/Contents.json b/NBox/Assets.xcassets/AppIcon.appiconset/Contents.json index 3f00db4..64dc11e 100644 --- a/NBox/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/NBox/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,51 +1,61 @@ { "images" : [ { + "filename" : "icon_16x16.png", "idiom" : "mac", "scale" : "1x", "size" : "16x16" }, { + "filename" : "icon_16x16@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "16x16" }, { + "filename" : "icon_32x32.png", "idiom" : "mac", "scale" : "1x", "size" : "32x32" }, { + "filename" : "icon_32x32@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "32x32" }, { + "filename" : "icon_128x128.png", "idiom" : "mac", "scale" : "1x", "size" : "128x128" }, { + "filename" : "icon_128x128@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "128x128" }, { + "filename" : "icon_256x256.png", "idiom" : "mac", "scale" : "1x", "size" : "256x256" }, { + "filename" : "icon_256x256@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "256x256" }, { + "filename" : "icon_512x512.png", "idiom" : "mac", "scale" : "1x", "size" : "512x512" }, { + "filename" : "icon_512x512@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "512x512" diff --git a/NBox/Assets.xcassets/AppIcon.appiconset/icon_128x128.png b/NBox/Assets.xcassets/AppIcon.appiconset/icon_128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..94a1d8c7ee3559a604496eab5aaf9c2f4c5a6c44 GIT binary patch literal 3015 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 literal 0 HcmV?d00001 diff --git a/NBox/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png b/NBox/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0630bb92031b65203696d050b86249508d86806a GIT binary patch literal 6210 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 literal 0 HcmV?d00001 diff --git a/NBox/Assets.xcassets/AppIcon.appiconset/icon_32x32.png b/NBox/Assets.xcassets/AppIcon.appiconset/icon_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..81b9090ee909dc6c7bee9b5dccca2cfaf0ed20e1 GIT binary patch literal 694 zcmV;n0!jUeP)DtnClg*BYe4vy3z@;(+;LW~X=VWClJ z8rnr(qtdao37OV_Wcv(=WVJCVe*+mmN@*#{+IBdh8HQoGuABjyDezS(0XK5F^tJ|I znwgaAG68J1T3Jf6QZlizky!$$KjhonmeJQ2pLd+H1tJP`0&4Zu!EAy20^+G{hi_%G c#@E`!A6O02PZj#gPyhe`07*qoM6N<$f`bf6Of*DHl)!~Tgb@6RiGpI#m}p87s0Au9Xkq}%Pj{EHZ9<_bwB2rZ z&m1qdEiJUPM9xg5eXjP*oH_H(yR+wf^X(bI2nYlMMYio!3Lz?h0FVnz9Z6e_v;fUO z#Br$dc*MSNINUH?BXO(BKp;?V+iVnq@;jwcGc`(yH=@z-k)ci))BwNVpABLMh^6jn z)5>>JvMLgZwBA^8lL3CezZm2Xz%(t=^l?^7DkG7|$^L}{49LqXoNif8Es&*Enm$^U zl4a3o=yY#Ep8=_9FOLDmMx^PZL8;{IXf)aaAg`B;9o+qc2e?m4&$iwJ0jayaQq-8! zq#vayk47W462P|EXj+hNq~v`75ex={$)p-KV7MExEKwwF+rC?#^|y^}JCAuBhYAy& zXZjQ`&W{1`@nIirF*!^NS6Jc!5zyJA5U{Ky&PxkA2<9jN9-R^Ai{E{0uk~Rot!fH{ z-ygUPWb5c|UQvQ|i#wPVNOCnM*m-Os0ME|pVv5hI`bnr|FOb&?5Z)fMS?M z(SM~NTk-PzP986b?>)1rL)}#|l zxcB{d+`4c;IanEE$bKC7r=iE%irE8L^7h2i&TRMAAOmIV{douvQ=LK7F z+k=PeZG40OTJ9J|9=@ZAaRW>XFwM9DrUjVBH3P163X%$zm&Vw=vW?ln#E9F@4kp;W zvW?}XF_J2EEw^wr5-iNI+3-jQ3yZr+*n-_Bylgp;LG!i2eb1e&Si7i$Cueq(W;q-@ zWET7ZGn(`Rg8H&Fydf4)-k9bnBesL!c6{O<-&+1a~e`mL= z>$5(z8PJPAOY!Q$4wja5BT03w5bXWa%jSa_G`8x!FKaiTm(pN@ub#OIz|)^kp)RB? z2QUCv*8;bqE|kWlcG>sY6ytG#X#u7gH^8(2(~KKnT7YTByaC4Ifc}&fC9ni^CE<8j zZ%km^082W2v!V?Eb2nun1$5hb7C`+OHTMA9f#$3F zH)?=KItT%;@0)}rA*Lw}Hb8^+9{bVJGvKfPa6kDu6vk3wqXh|!Wdj@uTrsV}9HK=6 zO{Qg-Lq#PJF)hO!;(~M>XTNC~2C0&1Y1a3qRhYuE#E&u*3WZ>7?G;EN4u!+vhMx7O zdW|vmI8=(apeG;D*w|PH>@})LBSNsJcQ0XI20}_!fv7hpOBbSE2v+wU^{>j$4^ER( z9pSFtHfPO^MjQU=Ef|o4O6{jprNHT%n*RSeYujR8|G~o`Il;!p#xqh<27Iecn!7kc zu*c((vznU1r*5pgTg4ml2QUc4ASI^-E6xlGT_vPz%=2r|02 m&xe32+qU*KH8q5XYxFN1Nx{`AS4(RE0000w`OLo`RAz%j_1^=vs3N;RqZcGOGA z@DdM@5W!#DZvBVw7mCrQACqW-z^Z5%X&{=NDWwsPC`t8g zF!#t_O;f(xJK%Y1@7T=QFY(8s^ZlXP%ABsfEq`b2y$Lf>=fun|iM`_xEujmd3n_EO z#l;sa)dI>hkiB7}z zs7v)WE0wJi((IduxouaPi;LY8jVmSjs12KjLxV7YI8K+d{HIa1%BH^ZF0ZW%Ds2{h zRSroH2<#HlG%U9xt*%8KD@t^Grsd}5>Re*RV`c^fTybjLrGsK!)C@NkNtAP01m#Vx zg2;rBv=)|en|z;n_gz;B2?_6eP#zM%8m*Fkp=hwK(Wq9AzeV{;IED>m$qnZ8QJ-`f zdEOUa9UmPSDCfh*=bS^Bhlz@cVoMCqSd%g%SQ1$tyYvepq@xaV)65KW%^sGf)(@9F zY}e{1)Z@pK{wtcxkiRe^W6s%jhqkpwTiY8BMp-Jbcn0%fxDz@jh9@Vb=v`!@`~k0Q zt5u2V+eGUSi58>$_x+SE>HsMB2J&HgjGXMn9UNAdm+6BsDJs%nIN)T@@vPdQqfVyR znDtf&<{D@yBVs`=9nIAr9b&G9U}B#ufY`>FreHc-Te_~OWkPh=TR2LZuVanX+tSPH zw_+ZZ8#i793j+g#>X!SN8B48k@7AXRVxV>L6}oQa=%9OhXJUk93gkt_c?(D)I3{;v_Gv!@b=mFmlOmvZm;C^{|d4Abo_1zMiw&Hp* zU|yJK<+v`s#W+4b?)w%KTXctyge{_#5bc_s<{Nc69H)%oCabqpjH*fhr9WyXH;(=HbP z`T5xq;+ke?XQlpf064?T4}H-Gl@k*MW2ptKBzjZ;dV6ku>ok)UnZkK|gaC~XLwuHx z-nee>?m7fxqP25!jB%6n<(CWFT40jDFaoXfH(A!mJ3DV_Qe3zoPVd5(u6^az@6Flv z7^~7Zg*2%Gp9%&i2;kj1|lUtf90}mrz^_}BeWj4A#R^P(K|NgZQ1{( zL%xX!6&4nTN!&Y}Oh13>38h3wZM#f%*B2HRI7 z5!am8pCP(?WzWRR*m%qx?OI8Fi~v2xi$@s8k&=G8Pux}&oQoVNH~zT8G%Lv)*=$$~ z0ERgpgbWG^SC(D#T9>JG7%giJ$E3t%1sVg@7;Yqe_|UOZZ_vFVJkW64hc4W+aB+G0 zC%p^vk@hZvDNd~b-4TjbmPe8TAn;@UXim@D8%d#n{O;W5K`zZ1C2F|o(>_H3vY7Id zY~Q2P$;&@`v``1ne)3)ZL9E_^y1Ex>$qR6Ds$ z|9-Cc5Me>}w{EEl#Jx0o zWP$&&L`ws~RAFa$BplLHFNY)nfc1r(lT*7)Tt`DUOH`Ak-^0IvexYe%V9O<`ciW>` zwg|w)6i{APM(?ryg*cxlpce1CFWFOEaCIhBnrPmT0;sMQ8n}!HPgyOucLNJ3fOP)_M+XPf$W|+du$h_n1St3>hvjx1djUC+KHyc9FV~b_ zfCjho4Nl8#tm6h?_B&KqUA^<4* z@&gaL!f5*FFflE#y^L6N$@B3y(Z3HrRm9hU<5$%Sa~yCEwe8*UGf=IFmMv_48=uuZ0?n2w`8)J zZ>-4(CANsd$eM&#cZ31BP%;?b=jmDPz&Wd|DyqbT#5g|_H{A+MOHUW)0U*p%^nD;h z#j{_{r`m}{X_dgDINmW3E@G7S7*9;QZ+henE<^Ch)>f>AI`Ll476PDJJEYhmR?ZAc zN~302U|xG!K9L-Hk9~{^4@_lsCS}&It*%Z;4qb|}B5I&sDFy&o2kznbeOZP=Pw<7=*Y z!95R)erbi9l~tKk1kLBd;Y*iKS|wnidskDhWTdq$0)TW+g|YFS&y|&~#(cz7-lR~1 zOBU6%GDnKVoYDz=@n=RYU%0uZa;wO9M~@KBU7dF7>iCoot-G86d% z@F_a5>9{>u=++(`t1_Y9Do(fQ&kS!H~ zsOELc=&r0D{&dv9@0dmh8fw%Zj~^E6oi1XJ-iNxGK6nD5_?{?Vdd+4w(iU` zI!`8TZ-3&BGfj$iM`3ohn2i}veCsU~sfrdE03<=>yZJ{Ron@8ah2)`dYRfsO$-(f@ z-!G415xkB!U1Z>dZb|puemVQr4bi{_YRKd6jh>6aumeE(+1|xPuwBS*m{)Ruan*&W z!DvnIpD*2_XJnLearFH>1k6KBFR4kuX~+gWJQ>FvG@J& z!M8{P__`U!T#fO6Oia|(XchUA7qUQw{O>Xk7C-K5EC3E@v2NhY7d>FyRAOh>kw ztz89qJ7E-+^l%`0ym?<@5bU?SF@zQ)>2$C>F0}>d*5CZCssUyc@Rlr>wNW(&BcvQ* z4MO<3-4!UMcSNUCK4{!wr*feIcO$#UY-|uS)jkjM9~?41ZIB5j#cYN!=RfIerC#ir zc+Guam?~r!B#)#;q1=+#RQ>yK4k*pnM!3pIZ?>(WJ7h|QmcJ?dVX zb6x(S9zKA5Ydp6Y_nsr+HC`58-C1R^&yXfYnN3HGMNm=aA}YmALG)<-8PaF;-IE#p z@{MdW;UwXyVqSh?7WtQI-W_HvuPO{gh&Ldvi5BWKaZfG*(ic9psE-@bTRBIpy+aivt1Sixa^~JxgKq5iM5CTGKUpy()CNmI^M@L8&U=*y_UWPQ+{yF50>dEd{#@J>qJqMWkqlgog{oJ#3Gz zZ-{M_y+?&_KB*yjJ6AJVB(2#f_fXEAtBtNx`Ioz9%$Y@!&!$$soqHsqIc-vAR0c#? zYPOx2tYAcuEAcJvg`9F(qvG3{{_$D4aidCu3)%U6IGs+*9`jU_BiKm?vD=@^O%vp- zh}(LOJ#s0M#6Oi%FzTrPjNUna>`$8;mG7QkZ7yB!bNyrZ&*7~6udHsD5z?*hN$O4U z3C4p;q7WE^WA&e<9xLxH54#R%U09u3-s}0odY#bP#tgF)`dMRmymg{evKc-~ZON)=){$yoZDE35+uHswS zGdfLD@bGuf=W^?NtX?G`XZ*KNt<2;+V<#AR5P876!iTmQxUjLH4lwGgzYIffv z4bc(_p4LdKy@qTSi{8^)`tt?X`Vgev<3mwD*u%;3{^Iv$i&K+eUtG^pMVJWm3Du_a z#|wC*+$bP*e5AW}En#nY==aiFL0ONHEt^U4M-`96$Rghysq@E;GA%Y_-9(sZt0hcW zgj$8Z2%$wf%SQMd^>2^uT^oPjXVXR7>l6B>!BO_d3+ljrGd2D=?EAxp_GMD9 zT5h3T=TdyREY`q4OSYl%rb4NLp&4eg!u{|Iy<0)hB}9X1W6HWq~GVe_1rP9cC4JTEdzU?C_}f*U9vS zFk&vw1uR?$Rle&9S9@MilMq3)_>vb6oJ}iETyZV*EfV@+yVBkoVC^z68dUF!6ko_B z#JTPwgj}!Sz(f7ED{T60zB7YF-C?{rUx8d#4Kda z9_4x21{a3s)AVi-&*AwRnO7<@JQgFn{074QyFW`s*8=&x7b5=}_;%NTT*~2gq#(x1 zJJshzu0e;4brX&6bl5Q})9F~mEPldUJkk^6t5aAwp;u`4QQTS{1$V~4dSM^m3G-wn z2TBAdsv0Y!1%B2?(%+Q&Ae*+MK^P;Z0 zC#U+prWOqD*z*XW_1WGd-8_C*H=Z|vOu3AKXjcxP`Wo%}ZW2zGW%j-vw+gKI+Av88 zrqZBg{l{_lMy!-Rt?)Aa1p%m&>Ftc%!3%^wXNK`^3MZ|nvD`Hum3>DV`?s7gn40_$ zv?0eHsQNj6D5{L@G6PbnB(}Ax~z8L=;;Ho2;cs zDskVl+STgLE0sMLwaycOJ2$f^wF+=@zf<>z&;k$8-^WsFox|P9P9Wpk$G=1Zo||~{alxLZ~V#Rz}S}P}CFvY67S=(H? z<4>K!%99jBx{4pklI-JSFnz8q-Hwk4v+GvnFU8#AjrrnRai)ogLeT)-#H5=Q*t&w( zlCFwh^LG1_SJb2h1>r|a?fG?=r+lqAGxrkjeELPvk-d@{Jg0wK%bEmaiAvnJ^WGgQ zCC3vuhZy(?ZP=04s>S!}?DtDwh~smhG4yPngW-olGUkrV4N=e%))+R~QikJ9+I?4M zkTOaMv|KXIgX)gkBrFz}n5Vw$nz}s8-Kgp5^v&s+t$&nOYZqtq!+fVlC%}1h(ngP} zvh8Zf>QdMe`J}DB1LLbRXZ$m60-#F=zt-i9Fpu#RyL6c8SfO?lFV}%JE@bG3HI!=N zN0#D|!PNb8`sH&jorbbl#_jF7HQ^Pf^|K9QB4@Rh3F1?@o6QocN&TWM;QqDyN=VmP z@*HK342Q%h)ewhSlGLxE?%!vGDYR8RD2ULO{UPW>y@DHHa3}D;F8?QXSeAz{q;|;0 zc}&dpk)Ew2_BpR?uF)1|B~fe}-i{0$$!bTmU4`lIE*zM+B_N9VJ5x61_fB8sg@r8} ziYGGo_$pO6*H@JiyPQBTefM!^=zHJi+I@onc*?@mLAjS#cY+BCQ=8RGF5*|nQ8man z6e+0Y@ltRUbC7%H^SsU9CT*zZk%?>V8cppJd3|z0{UqTH#}`(dg=_W9skC>&I(>?g&o(TXkb>V%@K`qOH(V3-$!p;RnN7^4H2X82IU$d;hdYwCOjZ?b6 zE+xf3zP3J^5~zg&RF0FQjm2O~hx2-J9z>+hiW+Bot1E{M%3sO2nFBrbsp!Q%P1d!RBx#V&<~^@TS- zc8~X@OiIcF=^eN*bJVE_MkO&>EU(V2{x)2ku6xaW_&60)*)r!Psg!@KPO`#Usf96C zNA^AbnU$wMDjN^rOPNRm)TNB`c*Q&>hd4%a!pdK{@L%)VKl_PS%cZRM*5 z7k*m3-E>96f5_SAF!~M@(LS*=F9p??^CBVK zUVoY>>0|qfsHN%_ui?J{gIkNvXS)B2M7Fp|h+*|8JA6G*jfd$LkJ_lYSI2~O@_be2 zu35W{;u_GkX%WLz7&SFC$C9cP9h@rP2D4Kiisz-QC#^ z{@u~tP5%K5T@zjt`yXJ4y%l@vP18vu@#E4^(~{d={CBWII^Z@k*i!$p&pi~!*UF-9 zCkY(xTsr6dJ43;q;kl(*!NHC`qqw`A4Wnd6{AY46hGwO~M=gOpO-l*R^F+d5uku2? zQAF(6Tjs1z(%1X1u<*pZkm^deQlZcEXKpbUrJ&fPQb*rx^}6xRe9tAltlqVoT3qmL zs!kXEHK}KE^9+0tLS5eSJGn>GO1nZWh*|K@$nm6KSqS?e=`{Fm*Y<jq|dssa0k5oQAa`d5~+_jtid##@_k8hqvbv`?e*D1 z_t$a?Kc^7L1p#oG`8aBMcG4=VU)?jKbnMb)VWB5WMRb~4AYBfCxv7*ULMV^Y&FHzi z20!(a)cyVJMg^iEe9|$tIBdWuoVYtR=O?GVcWK%rb@5yztf`dmzQUaCvGQ(|ifD3FXn{@kf$$3Sqy!XR`FiBVXI-d(SO0sVTIqcfARX9fCuU+7@v$gj_eTa* zAcV<*(y{gdHhos?9T^g3i-e!xS=aT8FUF=9WdAatO-#EiVP*CoCN|+}{iTou=q{>2Ru>w)_F<-oeY~ z((n+hBdEI@+a?guZ(=K0SW)6De#5U>PWbCsa$bmoKk6nFkmh{#%G2x~_yu41g^)Mn zFWd?q^^xjcfQCchgy$OF#*2b5TlXFB*oE%PP}Mf*VotST`+z-vDD6Y&!z-K}6<;IZ zBv}{WB#m!IUc|yDJ5UcjriF^HP|X|)c<^&Q-9bz(!8FC=FM#5_UA6MGxDbr2%+hTPpt)|r#E+1gCPK+A_08BOjJf-}v%m2T2&=SYO?pS6F zBYGgphV1d>uB;)ry5wAc8JRDo|r)Y)i$iJ*?BzdQ}%CE;h zd}v`@%0ain953rag9#sOE~zAQK3|VXlk0fW`S#h#ujyZ`1?Q!+DFIM>e0H7qtkuYP z&gTO7!t|ezCAK%Cnh#L=kF>=$%mc0F=p>h31wgE?>Wv|Czk#3NMOd ze8Va<4C5iw{}7Onz*F-7V}O2}g$Md+@(-LTqA^2ZhrnDKxjWAl`vlB=gLdo z6%cs|X201wA6IYo7LdgSb6VTU<_4|zMpqogg4yR%nR=g3Yz=QgBz}ZihQTWSovAcv zo~hw`wM=nCHSy!V%K@bd@Ova=&h9*3ed1 z0oM;D(d4ogtA*5yKX&IA6&;l-I!89{0y43xv=4d7ZMnMjSN-g3=Hyki-sQin}p*d~d$ zY4KQdtk0g5*SP>kyyEkvTS_JeNjA7I=@z^F{8_s+)J=p+N=lNnz43w=rVKqBSsR=A zIi#7IDSpvdKOC2J%`L81 zQTD|kt($Nr8PB}JwZicT->(TX@|CZ3p`A(Icy|q-et?~v6Ztz$f0SF2u(9*i0D(;w zt}3+W(qX=$Ec_l{4p2f=r0B$bykRPs@C zEJw>W2fp-2t^KOBY3V;-ri(nUQ?Qc24|<$GIAQ~+85s}Wr!lZ*7_;xKzMa=aPDVdq` z;b?~>J)C*XUm!I#yS+~SB;i&cX~in0K2aoZ1{@>oj*kbX>US=T>Q zxe;U$c8e1zb$8*}-Q0BpfcHkXf$_duF>%XViKph~=I|Kop;~WtQxXin%PmH=g_V`_ z1s37LPBlK_xFht(v-I8Yk@c$PqVwK#{8x{fPmYM?$jZSQMe(Z;>KS-hE{ol6Y1)^c z#2bG2^r^X}larGQKhKAyDwsI8^90WlHC)ezOu7YzXn?Y|y86rs+C%jjqzM$=Pq=t) zYxhmktOx=;O)ST1vRItyw>d@M26huQ?!kP!p)=Xg>ncsOmJ>C9 zI_pVgEVh8H-HKmRP`HvS(%Ct?n;fR4yz=Cgm6g-y+1;H7St8x6Iv+ji5-tUl&8PZo|p&_v`tDV9@MxV6EgaqT-9sw~$sMZ_$R7&g=^ z^2$9_wO-XEXM)-i@g$O_6b7|an!?oP@@5&Lp{04bq#!n>YH zB6_`HypS;^P1r{LE4kQxYwp$q6Rt>T9~@^Xx~7!n;aA8b16QukABl>=1w}hZh>s-r zoT+B5XrtPGEU9ec?~a7vez@z(**NRiyf52ba)6F(B@-X+@23}TK9(IGA_6I7$ceGB zlY=52S`Y9p?aP-A2Jveo0Cl1*I_{)=`Fyg>bK5$xWY$ANA-~cucc^^+}Yk1pF4Up(7bT}52tCnBT-PW*s{%gJ%;o5>EYDWloFf3`m4FQ zXd~s8Y&>hYf;5WJb)T(TXSU9c=cQn2v}WUUV6unwkUpa=r1CA?G^jLY+nu?IiB(2L z83XsxAdWogMv9IIlTd))!C{1_&Mc0a=Iql&e$*J+k| zA7zMuR&ib$&)~p7Qs2PN&}mjMhe8v@hkwXwlTrP&IF23GCjT&$&ru_@+93=RU6u8n z2GvSLOnf@}L+ebUd+S1E$D}-x0v?GW9qoP89UTv+q2Y4hCL?0=$^~68^NTmpXq>=V zoC)~@)gGSJh2fZY+S&X6Op|0%_Gr5DJYl^|@B5q*6h0tHjM}b8`zkm(cAwm%(oxY9 zlZK4EODt=uXnh69`AWGMrx>zvmJbtd<-PkCU+0hmG57Z9bW@v7||MXHe9^dO(RT)!|n zbrSNhtU`SB*kgnL;YC8Kp#+X<$nZG73Z~%a$DRmqp&0G3DUKW*A4lrlJhFKuquC1y zoJo#q%Zca-wrp-JtfDW!t6&DHJ@sJqu!KxQLCBoM(W21pS0P*s^$H(wcGPMkO>6 z>yeYCGZK>MZqW(ZQA5U_iZo!q%O95)W06T?*7-M!%$Clj<#R^a>%q>V z614yDPF**2IbA~5mh{jX=M$z4EV;OTjE`o=Fzu(|Hrp37@KBi3wI zEC!@>EEnmAyC+q38;xHHPpB3X6BAZM>I(i1oQ~!?Hy02oy8uvA%m{`eL1j3o8 z4Si@))~(3$-FwR6hd#?a>Ip(G?87ML-xn- z)8<#a+=JO@X<5HrItLns(}Gsr3Dxb&{#GyR8E1RE)OF?{(Co0|-Nz98_}Biv+l}sD cG2v$v8G3paRKA~-VA%r7@)~jlcg_9&4O0bwAOHXW literal 0 HcmV?d00001 diff --git a/NBox/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png b/NBox/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c8affbe60d91befa85510bb88ce4a8799a990211 GIT binary patch literal 27630 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 B;EDhM literal 0 HcmV?d00001 diff --git a/NBox/HotkeyManager.swift b/NBox/HotkeyManager.swift new file mode 100644 index 0000000..238331b --- /dev/null +++ b/NBox/HotkeyManager.swift @@ -0,0 +1,55 @@ +import Carbon +import AppKit + +/// Registers a global hotkey (Cmd+Shift+K) using the Carbon API. +/// Works in sandboxed apps without Accessibility permissions. +final class HotkeyManager { + private var hotkeyRef: EventHotKeyRef? + private var eventHandler: EventHandlerRef? + var onTrigger: (() -> Void)? + + func register() { + let hotkeyID = EventHotKeyID(signature: OSType(0x4E424F58), // "NBOX" + id: 1) + + // Cmd+Shift+K — kVK_ANSI_K = 0x28 + let modifiers: UInt32 = UInt32(cmdKey | shiftKey) + let status = RegisterEventHotKey(UInt32(kVK_ANSI_K), modifiers, hotkeyID, + GetApplicationEventTarget(), 0, &hotkeyRef) + guard status == noErr else { + print("nbox: failed to register hotkey: \(status)") + return + } + + // Install Carbon event handler for hotkey events + var eventType = EventTypeSpec(eventClass: OSType(kEventClassKeyboard), + eventKind: UInt32(kEventHotKeyPressed)) + + let handlerBlock: EventHandlerUPP = { _, event, userData -> OSStatus in + guard let userData else { return OSStatus(eventNotHandledErr) } + let manager = Unmanaged.fromOpaque(userData).takeUnretainedValue() + DispatchQueue.main.async { + manager.onTrigger?() + } + return noErr + } + + let selfPtr = Unmanaged.passUnretained(self).toOpaque() + InstallEventHandler(GetApplicationEventTarget(), handlerBlock, 1, &eventType, selfPtr, &eventHandler) + } + + func unregister() { + if let hotkeyRef { + UnregisterEventHotKey(hotkeyRef) + self.hotkeyRef = nil + } + if let eventHandler { + RemoveEventHandler(eventHandler) + self.eventHandler = nil + } + } + + deinit { + unregister() + } +} diff --git a/NBox/NBox.entitlements b/NBox/NBox.entitlements index 9b70eed..de4bbf4 100644 --- a/NBox/NBox.entitlements +++ b/NBox/NBox.entitlements @@ -3,7 +3,11 @@ com.apple.security.app-sandbox - + + com.apple.security.network.client + + com.apple.security.network.server + keychain-access-groups $(AppIdentifierPrefix)dev.nobox.nbox diff --git a/NBox/NBoxApp.swift b/NBox/NBoxApp.swift index 1951856..33f820b 100644 --- a/NBox/NBoxApp.swift +++ b/NBox/NBoxApp.swift @@ -16,11 +16,18 @@ struct NBoxApp: App { class AppDelegate: NSObject, NSApplicationDelegate { private var socketServer: SocketServer? + private let hotkeyManager = HotkeyManager() + private lazy var quickAccessPanel = QuickAccessPanel(appState: AppState.shared) func applicationDidFinishLaunching(_ notification: Notification) { socketServer = SocketServer(keychain: KeychainManager()) socketServer?.start() installCLI() + + hotkeyManager.onTrigger = { [weak self] in + self?.quickAccessPanel.toggle() + } + hotkeyManager.register() } /// Copy the bundled nbox-cli to ~/.local/bin/nbox on every launch, @@ -46,6 +53,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { } func applicationWillTerminate(_ notification: Notification) { + hotkeyManager.unregister() socketServer?.stop() AppState.shared.cleanupSessionDir() } diff --git a/NBox/QuickAccessPanel.swift b/NBox/QuickAccessPanel.swift new file mode 100644 index 0000000..73eb20e --- /dev/null +++ b/NBox/QuickAccessPanel.swift @@ -0,0 +1,249 @@ +import AppKit +import SwiftUI + +/// A floating panel that appears near the cursor for quick secret access. +final class QuickAccessPanel { + private var panel: NSPanel? + private let appState: AppState + + init(appState: AppState) { + self.appState = appState + } + + var isVisible: Bool { panel?.isVisible ?? false } + + func toggle() { + if let panel, panel.isVisible { + dismiss() + } else { + show() + } + } + + func show() { + dismiss() + + let view = QuickAccessView(appState: appState, onDismiss: { [weak self] in + self?.dismiss() + }) + + let hostingView = NSHostingView(rootView: view) + hostingView.frame = NSRect(x: 0, y: 0, width: 300, height: 360) + + let panel = NSPanel( + contentRect: hostingView.frame, + styleMask: [.nonactivatingPanel, .titled, .fullSizeContentView], + backing: .buffered, + defer: false + ) + panel.isFloatingPanel = true + panel.level = .floating + panel.titleVisibility = .hidden + panel.titlebarAppearsTransparent = true + panel.isMovableByWindowBackground = true + panel.contentView = hostingView + panel.backgroundColor = .clear + panel.isOpaque = false + panel.hasShadow = true + panel.hidesOnDeactivate = false + + // Position near mouse cursor + let mouseLocation = NSEvent.mouseLocation + let screenFrame = NSScreen.main?.visibleFrame ?? .zero + var origin = NSPoint(x: mouseLocation.x - 150, y: mouseLocation.y - 380) + + // Keep on screen + origin.x = max(screenFrame.minX, min(origin.x, screenFrame.maxX - 300)) + origin.y = max(screenFrame.minY, min(origin.y, screenFrame.maxY - 360)) + + panel.setFrameOrigin(origin) + panel.makeKeyAndOrderFront(nil) + + // Activate the app so the panel can receive keyboard input + NSApp.activate(ignoringOtherApps: true) + + self.panel = panel + } + + func dismiss() { + panel?.orderOut(nil) + panel = nil + } +} + +struct QuickAccessView: View { + let appState: AppState + let onDismiss: () -> Void + @State private var search = "" + @State private var error: String? + @State private var copiedAccount: String? + @FocusState private var searchFocused: Bool + + private var filtered: [SecretInfo] { + if search.isEmpty { return appState.accounts } + return appState.accounts.filter { $0.account.localizedCaseInsensitiveContains(search) } + } + + var body: some View { + VStack(spacing: 0) { + // Search bar + HStack(spacing: 6) { + Image(systemName: "magnifyingglass") + .foregroundStyle(.secondary) + .font(.system(size: 13)) + TextField("Search secrets...", text: $search) + .textFieldStyle(.plain) + .font(.system(size: 14)) + .focused($searchFocused) + Button(action: onDismiss) { + Image(systemName: "xmark.circle.fill") + .foregroundStyle(.tertiary) + } + .buttonStyle(.plain) + } + .padding(10) + + Divider() + + // Results + if filtered.isEmpty { + Spacer() + Text(search.isEmpty ? "No secrets stored" : "No matches") + .foregroundStyle(.secondary) + .font(.system(size: 13)) + Spacer() + } else { + ScrollView { + LazyVStack(alignment: .leading, spacing: 0) { + ForEach(filtered, id: \.account) { info in + QuickAccessRow( + info: info, + isCopied: copiedAccount == info.account, + onCopy: { copySecret(info.account) } + ) + } + } + .padding(.vertical, 4) + } + } + + if let error { + Divider() + Text(error) + .font(.caption) + .foregroundStyle(.red) + .padding(.horizontal, 10) + .padding(.vertical, 4) + } + + Divider() + + HStack { + Text("\u{2318}\u{21E7}K to toggle") + .font(.system(size: 10)) + .foregroundStyle(.tertiary) + Spacer() + Text("Touch ID to copy") + .font(.system(size: 10)) + .foregroundStyle(.tertiary) + } + .padding(.horizontal, 10) + .padding(.vertical, 5) + } + .frame(width: 300, height: 360) + .background(.ultraThinMaterial) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .onAppear { + searchFocused = true + appState.refresh() + } + .onExitCommand { + onDismiss() + } + } + + private func copySecret(_ account: String) { + error = nil + switch appState.revealSecret(account: account) { + case .success(let value): + // For structured types, extract the most useful value + let meta = appState.metadataFor(account: account) + let copyValue: String + if meta?.type == "login", let decoded = CredentialValue.decodeLogin(value) { + // Copy password (most common need); username is usually not secret + copyValue = decoded.password + } else if meta?.type == "api_key", let decoded = CredentialValue.decodeAPIKey(value) { + copyValue = decoded.primary + } else if meta?.type == "recovery_code", let codes = CredentialValue.decodeRecoveryCodes(value) { + copyValue = codes.joined(separator: "\n") + } else { + copyValue = value + } + + NSPasteboard.general.clearContents() + NSPasteboard.general.setString(copyValue, forType: .string) + copiedAccount = account + + // Auto-clear clipboard after 30s + let changeCount = NSPasteboard.general.changeCount + DispatchQueue.main.asyncAfter(deadline: .now() + 30) { + if NSPasteboard.general.changeCount == changeCount { + NSPasteboard.general.clearContents() + } + } + + // Dismiss after brief visual feedback + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + onDismiss() + } + case .failure(let err): + if case .authCanceled = err { + // User canceled Touch ID — just stay open + } else { + error = err.description + } + } + } +} + +private struct QuickAccessRow: View { + let info: SecretInfo + let isCopied: Bool + let onCopy: () -> Void + + var body: some View { + Button(action: onCopy) { + HStack { + VStack(alignment: .leading, spacing: 2) { + Text(info.account) + .font(.system(size: 12, design: .monospaced)) + .lineLimit(1) + .truncationMode(.middle) + if let peek = info.metadata?.peek { + Text("\(peek)...") + .font(.system(size: 10, design: .monospaced)) + .foregroundStyle(.tertiary) + } + } + Spacer() + if isCopied { + Image(systemName: "checkmark.circle.fill") + .foregroundStyle(.green) + .font(.system(size: 14)) + } else { + if let type = info.metadata?.type { + TypeBadge(type: type) + } + Image(systemName: "key.fill") + .foregroundStyle(.secondary) + .font(.system(size: 11)) + } + } + .padding(.horizontal, 10) + .padding(.vertical, 6) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .background(isCopied ? Color.green.opacity(0.1) : Color.clear) + } +} diff --git a/NBox.xcodeproj/project.pbxproj b/Noxkey.xcodeproj/project.pbxproj similarity index 89% rename from NBox.xcodeproj/project.pbxproj rename to Noxkey.xcodeproj/project.pbxproj index 9fc8e88..7eae64c 100644 --- a/NBox.xcodeproj/project.pbxproj +++ b/Noxkey.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXFileReference section */ - 0228B8292F5B61030021FBA2 /* NBox.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NBox.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 0228B8292F5B61030021FBA2 /* Noxkey.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Noxkey.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ @@ -40,7 +40,7 @@ 0228B82A2F5B61030021FBA2 /* Products */ = { isa = PBXGroup; children = ( - 0228B8292F5B61030021FBA2 /* NBox.app */, + 0228B8292F5B61030021FBA2 /* Noxkey.app */, ); name = Products; sourceTree = ""; @@ -48,9 +48,9 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 0228B8282F5B61030021FBA2 /* NBox */ = { + 0228B8282F5B61030021FBA2 /* Noxkey */ = { isa = PBXNativeTarget; - buildConfigurationList = 0228B8342F5B61050021FBA2 /* Build configuration list for PBXNativeTarget "NBox" */; + buildConfigurationList = 0228B8342F5B61050021FBA2 /* Build configuration list for PBXNativeTarget "Noxkey" */; buildPhases = ( 0228B8252F5B61030021FBA2 /* Sources */, 0228B8262F5B61030021FBA2 /* Frameworks */, @@ -63,11 +63,11 @@ fileSystemSynchronizedGroups = ( 0228B82B2F5B61030021FBA2 /* NBox */, ); - name = NBox; + name = Noxkey; packageProductDependencies = ( ); productName = NBox; - productReference = 0228B8292F5B61030021FBA2 /* NBox.app */; + productReference = 0228B8292F5B61030021FBA2 /* Noxkey.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -86,7 +86,7 @@ }; }; }; - buildConfigurationList = 0228B8242F5B61030021FBA2 /* Build configuration list for PBXProject "NBox" */; + buildConfigurationList = 0228B8242F5B61030021FBA2 /* Build configuration list for PBXProject "Noxkey" */; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -100,7 +100,7 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 0228B8282F5B61030021FBA2 /* NBox */, + 0228B8282F5B61030021FBA2 /* Noxkey */, ); }; /* End PBXProject section */ @@ -262,13 +262,17 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Noxkey; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; + INFOPLIST_KEY_LSUIElement = YES; + INFOPLIST_KEY_NSFaceIDUsageDescription = "nbox uses Touch ID to protect your secrets."; INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = dev.nobox.nbox; + PRODUCT_BUNDLE_IDENTIFIER = dev.noboxdev.Noxkey; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -293,13 +297,17 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Noxkey; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; + INFOPLIST_KEY_LSUIElement = YES; + INFOPLIST_KEY_NSFaceIDUsageDescription = "nbox uses Touch ID to protect your secrets."; INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = dev.nobox.nbox; + PRODUCT_BUNDLE_IDENTIFIER = dev.noboxdev.Noxkey; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -310,7 +318,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 0228B8242F5B61030021FBA2 /* Build configuration list for PBXProject "NBox" */ = { + 0228B8242F5B61030021FBA2 /* Build configuration list for PBXProject "Noxkey" */ = { isa = XCConfigurationList; buildConfigurations = ( 0228B8322F5B61050021FBA2 /* Debug */, @@ -319,7 +327,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 0228B8342F5B61050021FBA2 /* Build configuration list for PBXNativeTarget "NBox" */ = { + 0228B8342F5B61050021FBA2 /* Build configuration list for PBXNativeTarget "Noxkey" */ = { isa = XCConfigurationList; buildConfigurations = ( 0228B8352F5B61050021FBA2 /* Debug */, From fe953b33d8197a6e6ee3caa300a791cc259b6ab0 Mon Sep 17 00:00:00 2001 From: Jasper Middendorp Date: Sat, 7 Mar 2026 20:39:11 +0100 Subject: [PATCH 2/2] Fix generic Touch ID prompt by using kSecUseOperationPrompt, add branding links Touch ID dialogs now show descriptive reasons (e.g. "Reveal value for ...") instead of the generic "NBox needs to authenticate to continue." Also adds No-Box-Dev links to the footer and onboarding, and removes unused ContentView. Co-Authored-By: Claude Opus 4.6 --- NBox/AppState.swift | 4 ++-- NBox/ContentView.swift | 24 ------------------------ NBox/KeychainManager.swift | 13 ++++++++----- NBox/SecretListView.swift | 18 +++++++++++++----- NBox/SocketServer.swift | 2 +- 5 files changed, 24 insertions(+), 37 deletions(-) delete mode 100644 NBox/ContentView.swift diff --git a/NBox/AppState.swift b/NBox/AppState.swift index 6450646..ac37d7d 100644 --- a/NBox/AppState.swift +++ b/NBox/AppState.swift @@ -69,7 +69,7 @@ class AppState { } func revealSecret(account: String) -> Result { - KeychainManager().get(account: account) + KeychainManager().get(account: account, reason: "Reveal value for \"\(account)\"") } private var pendingDeletes: [String: DispatchWorkItem] = [:] @@ -77,7 +77,7 @@ class AppState { /// Encrypts secret to a temp file, copies a `source <(openssl ...)` command to clipboard. /// File auto-deletes after 60s. Clipboard auto-clears after 30s. func shareSecret(account: String) -> Result { - switch KeychainManager().get(account: account) { + switch KeychainManager().get(account: account, reason: "Share \"\(account)\" (encrypted to temp file)") { case .success(let value): // Derive env var name from the last path component let envVar = account.split(separator: "/").last.map(String.init) ?? account diff --git a/NBox/ContentView.swift b/NBox/ContentView.swift deleted file mode 100644 index 837d544..0000000 --- a/NBox/ContentView.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// ContentView.swift -// NBox -// -// Created by Jasper Middendorp on 06/03/2026. -// - -import SwiftUI - -struct ContentView: View { - var body: some View { - VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") - } - .padding() - } -} - -#Preview { - ContentView() -} diff --git a/NBox/KeychainManager.swift b/NBox/KeychainManager.swift index d07d488..d0259e5 100644 --- a/NBox/KeychainManager.swift +++ b/NBox/KeychainManager.swift @@ -116,15 +116,15 @@ struct KeychainManager { return .success(()) } - func get(account: String) -> Result { + func get(account: String, reason: String? = nil) -> Result { let context = LAContext() - context.localizedReason = "Read value for \"\(account)\"" - return get(account: account, context: context) + let prompt = reason ?? "Read value for \"\(account)\"" + return get(account: account, context: context, reason: prompt) } /// Read a secret using a pre-authenticated LAContext (avoids repeated Touch ID). - func get(account: String, context: LAContext) -> Result { - let query: [String: Any] = [ + func get(account: String, context: LAContext, reason: String? = nil) -> Result { + var query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: account, @@ -132,6 +132,9 @@ struct KeychainManager { kSecUseAuthenticationContext as String: context, kSecUseDataProtectionKeychain as String: true, ] + if let reason { + query[kSecUseOperationPrompt as String] = reason + } var result: AnyObject? let status = SecItemCopyMatching(query as CFDictionary, &result) diff --git a/NBox/SecretListView.swift b/NBox/SecretListView.swift index a230e12..193a512 100644 --- a/NBox/SecretListView.swift +++ b/NBox/SecretListView.swift @@ -99,6 +99,9 @@ struct SecretListView: View { .font(.caption) .buttonStyle(.plain) .foregroundStyle(copiedInstructions ? .green : Color(red: 0xFE/255, green: 0x79/255, blue: 0x5D/255)) + Link("No-Box-Dev", destination: URL(string: "https://noboxdev.com")!) + .font(.caption) + .foregroundStyle(.secondary) Button("Quit") { NSApplication.shared.terminate(nil) } @@ -539,11 +542,16 @@ struct OnboardingView: View { Divider() - HStack { - Spacer() - Button("Got it") { onDismiss() } - .keyboardShortcut(.defaultAction) - Spacer() + VStack(spacing: 4) { + HStack { + Spacer() + Button("Got it") { onDismiss() } + .keyboardShortcut(.defaultAction) + Spacer() + } + Link("noboxdev.com", destination: URL(string: "https://noboxdev.com")!) + .font(.caption2) + .foregroundStyle(.tertiary) } .padding(.vertical, 8) } diff --git a/NBox/SocketServer.swift b/NBox/SocketServer.swift index 62227c2..0e8b5aa 100644 --- a/NBox/SocketServer.swift +++ b/NBox/SocketServer.swift @@ -287,7 +287,7 @@ class SocketServer { sendResponse(clientSocket, ok: true, value: cached) return } - switch keychain.get(account: account) { + switch keychain.get(account: account, reason: "CLI: read value for \"\(account)\"") { case .success(let value): sendResponse(clientSocket, ok: true, value: value) case .failure(let err):