Assets afschermen d.m.v. authenticatie

Pagina: 1
Acties:

Vraag


  • HollowGamer
  • Registratie: Februari 2009
  • Niet online
Mijn vraag
Ik vroeg mij af hoe jullie assets afschermen als je gebruikt maakt van REST/API's i.c.m. SPA (Vue).
Kleine dingen kan je met base64 encoden (bv. thumbnails) en sturen naar de cliënt. Maar hoe doe je dat met grote bestanden als documenten, muziek of andere media? Je kunt ze in de public map zetten, maar dat is dus niet iets wat ik wil.

Op dit moment gebruik ik Laravel met Vue/vuex + JWT en haal ik de data op met axios. Met dat laatste kan je dit ook in een base64 encoding gooien, maar dat vind ik erg omslachtig en het is mij nog niet gelukt, zie https://github.com/axios/axios/issues/513

Is zoiets ook gebruikelijk: https://example.com/api/assets/5?token=<key>

Hebben jullie een goede oplossing? :)

Relevante software en hardware die ik gebruik
Laravel 6.0.3
tymon/jwt-auth
@websanova/vue-auth

Wat ik al gevonden of geprobeerd heb
Het gooien in een base64 en gekeken naar API-tokens.

Beste antwoord (via HollowGamer op 20-09-2019 15:50)


  • DataGhost
  • Registratie: Augustus 2003
  • Laatst online: 03-10 23:11

DataGhost

iPL dev

Maar kijk nou eens goed naar de verschillen

https://example.com/api/assets/1:
• Kijk welk bestand hoort bij asset 1
• Open bestand
• Stuur naar client

https://example.com/api/assets/1?token=x:
• Kijk of het token geldig is
• Kijk welk bestand hoort bij asset 1
• Open bestand
• Stuur naar client

Axios/base64:
• Kijk of de client het request mag doen (ik ken axios verder niet)
• Kijk welk bestand hoort bij asset 1
• Open bestand
• Base64 encode bestand
• Stuur naar client (embedded in de pagina die ze openen)

Ik zou het wel weten.
HollowGamer schreef op donderdag 19 september 2019 @ 12:08:
[...]

Als query (?token=<key>) is het makkelijkst, maar vind ik wel omslachtig en zoals @DataGhost aangeeft, moet ik dan extra checks doen - niet perse erg hoor, maar vroeg mij af of dit de beste oplossing is. :)
HollowGamer schreef op woensdag 18 september 2019 @ 20:35:
@Marco1994 Kan je een voorbeeld even hoe je die bestanden serveert? Ik doe ook alles over HTTPS, maar die zijn dus nu zonder authenticatie. Ik wil juist dat iemand moet zijn ingelogd om erbij te komen. Dat gaat opzicht prima met native templates/blades, maar ik gebruik die dus niet en doe alles met Vue.
Hoe was je van plan authenticatie af te dwingen zonder checks?

[ Voor 42% gewijzigd door DataGhost op 19-09-2019 12:19 ]

Alle reacties


  • Marco1994
  • Registratie: Juli 2012
  • Laatst online: 17:41
Dingen die niet zeer geheim zijn, zetten we op onze cdn. Zaken die wat gevoeliger zijn, zetten we op het lokale bestandssysteem van de server en worden via diverse manieren over https geserveerd. Dingen als afbeeldingen voor knopjes staan in de assets map. Ik vraag me af wat voor usecase je hebt, als je bestanden of (grote) muziek bestanden in je assets mal wilt hebben?

  • HollowGamer
  • Registratie: Februari 2009
  • Niet online
@Marco1994 Kan je een voorbeeld even hoe je die bestanden serveert? Ik doe ook alles over HTTPS, maar die zijn dus nu zonder authenticatie. Ik wil juist dat iemand moet zijn ingelogd om erbij te komen. Dat gaat opzicht prima met native templates/blades, maar ik gebruik die dus niet en doe alles met Vue.

Het is overigens een eigen speel project waaruit ik dingen kan leren. :)

  • Marco1994
  • Registratie: Juli 2012
  • Laatst online: 17:41
Hoe het in lavarel werkt weet ik zo niet precies, maar je geeft al aan dat je jwt gebruikt. Dat zul je een login functie of iets moeten maken wat die token genereert en die als authenticatie header meesturen met elke request. Zodat je aan de api kant kan valideren of je authorized bent om van die endpoint gebruik mag maken.

Edit: https://medium.com/employ...b-tokens-jwt-cd223ace8d1a zoiets ziet er wel goed uit denk ik

[ Voor 19% gewijzigd door Marco1994 op 18-09-2019 21:10 ]


  • DJMaze
  • Registratie: Juni 2002
  • Niet online
Je kan gewoon een PHP bestand maken die de authenticatie header controleert en vervolgens de header('Accept-Ranges: bytes') meesturen met het bestand.
Op die manier kan een client hele grote bestanden in chunks binnen halen.

Moet je wel in PHP de $_SERVER['HTTP_RANGE'] dan ook verwerken, anders is het nutteloos.

[ Voor 16% gewijzigd door DJMaze op 19-09-2019 08:47 ]

Maak je niet druk, dat doet de compressor maar


  • HollowGamer
  • Registratie: Februari 2009
  • Niet online
@Marco1994 JWT werkt prima, enkel doe ik alles met Vue/JS, dus moet ik ook alles afhandelen in die laag.

@DJMaze Thanks voor je stream tips. Ik gebruik hiervoor Nginx zelf, dus hoef gelukkig niet veel hiervoor te doen.

Het gaat dus niet om het streamen zelf, maar meer hoe je tokens verwerkt bij het ophalen van data/media.

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
Wellicht overbodig, maar laten we de verwarring voorkomen: Base-64 heeft niets met authentictie te maken.

HTTPS werkt prima met authenticatie, en die kun je dan ook gebruiken om grote bestanden rechtstreeks op te halen. Maar dan moet je dus wel echte HTTP authenticatie hebben - de browser moet je credentials hebben om die te gebruiken. Als je een custom login form bouwt, dan weet de browser niet dat de username/password velden daarin de credentials zijn voor HTTP authenticatie.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


Acties:
  • +1 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 21-08 17:09
De meeste CDN's zoals die van amazon ondersteunen het beschikbaar maken van bestanden aan de hand van (tijdelijke) credentials van een user. Netflix bijvoorbeeld doet dit ook. Ik zou zo iets niet zelf gaan bouwen als Amazon en Google het al klaar hebben staan voor je.

https://niels.nu


  • n9iels
  • Registratie: November 2017
  • Niet online
Wat ik meestal doe is een API endpoint met authenticatie, het bestand uitlezen als stream en deze vervolgens met de juist Content-Type header eruit sturen. De browser snap dit en presenteert de content op de juist manier. Dat is efficienter als dan base64 en zou in theorie geen verschil moeten hebben met het normale bestand. Als de bestanden echt groot worden kun je gaan werken met stream richting de client.

[ Voor 24% gewijzigd door n9iels op 19-09-2019 09:34 . Reden: Loze opmerking verwijderd ]


  • Stoelpoot
  • Registratie: September 2012
  • Niet online
n9iels schreef op donderdag 19 september 2019 @ 09:32:
Wat ik meestal doe is een API endpoint met authenticatie, het bestand uitlezen als stream en deze vervolgens met de juist Content-Type header eruit sturen. De browser snap dit en presenteert de content op de juist manier. Dat is efficienter als dan base64 en zou in theorie geen verschil moeten hebben met het normale bestand. Als de bestanden echt groot worden kun je gaan werken met stream richting de client.
Dit zou mijn aanpak ook zijn, ja. Bestanden zijn uiteindelijk ook gewoon HTTP-responses en die kan je zelf samenstellen op elke manier die je wilt (en anders moet je op zoek naar een sterker framework). Dan regel je de authenticatie op dezelfde manier als elk ander request, alleen ipv JSON wordt het bestand verstuurd.

  • HollowGamer
  • Registratie: Februari 2009
  • Niet online
n9iels schreef op donderdag 19 september 2019 @ 09:32:
Wat ik meestal doe is een API endpoint met authenticatie, het bestand uitlezen als stream en deze vervolgens met de juist Content-Type header eruit sturen. De browser snap dit en presenteert de content op de juist manier. Dat is efficienter als dan base64 en zou in theorie geen verschil moeten hebben met het normale bestand. Als de bestanden echt groot worden kun je gaan werken met stream richting de client.
Dit komt het meeste in de buurt van het geen dat ik bedoel. :)

Hoe doe je dit dan zo ongeveer? Base64 encoden is inderdaad niet zo efficient, dit is de hoofdreden dat ik naar (goede) alternatieven wil kijken.
Stoelpoot schreef op donderdag 19 september 2019 @ 09:42:
[...]


Dit zou mijn aanpak ook zijn, ja. Bestanden zijn uiteindelijk ook gewoon HTTP-responses en die kan je zelf samenstellen op elke manier die je wilt (en anders moet je op zoek naar een sterker framework). Dan regel je de authenticatie op dezelfde manier als elk ander request, alleen ipv JSON wordt het bestand verstuurd.
Dit werkt deels, maar dus niet als frontend/backend gescheiden zijn. Tenminste ik weet het even niet. :+
Hydra schreef op donderdag 19 september 2019 @ 09:31:
De meeste CDN's zoals die van amazon ondersteunen het beschikbaar maken van bestanden aan de hand van (tijdelijke) credentials van een user. Netflix bijvoorbeeld doet dit ook. Ik zou zo iets niet zelf gaan bouwen als Amazon en Google het al klaar hebben staan voor je.
Dat is allemaal niet zo'n probleem. Laravel heeft ook al een tijdje dit aan poort zitten (signed url's) en dat werkt prima. Het is ook niet echt waard voor het geen wat ik ben aan het bouwen. :)

  • Hydra
  • Registratie: September 2000
  • Laatst online: 21-08 17:09
HollowGamer schreef op donderdag 19 september 2019 @ 10:20:
Dat is allemaal niet zo'n probleem. Laravel heeft ook al een tijdje dit aan poort zitten (signed url's) en dat werkt prima. Het is ook niet echt waard voor het geen wat ik ben aan het bouwen. :)
Ik ken Laravel niet, maar als dit inderdaad dan een forward is naar een CDN dan maak je nog steeds gebruik van dat CDN toch? Dus dat de data niet 'door' je Laravel instance loopt.

https://niels.nu


  • n9iels
  • Registratie: November 2017
  • Niet online
HollowGamer schreef op donderdag 19 september 2019 @ 10:20:
[...]

Dit komt het meeste in de buurt van het geen dat ik bedoel. :)

Hoe doe je dit dan zo ongeveer? Base64 encoden is inderdaad niet zo efficient, dit is de hoofdreden dat ik naar (goede) alternatieven wil kijken.
Ik ben zelf niet bekend met Laravel, maar volgens mij zou je in deze richting moeten kijken: https://www.php.net/manual/en/function.readfile.php.
Een snelle zoektocht binnen Laravel levert dit op. Als ik er snel naar kijk zou je met Storage::get('file.jpg'); de inhoud van het bestand moeten kunnen uitlezen.

Acties:
  • +1 Henk 'm!

  • HollowGamer
  • Registratie: Februari 2009
  • Niet online
Hydra schreef op donderdag 19 september 2019 @ 10:27:
[...]


Ik ken Laravel niet, maar als dit inderdaad dan een forward is naar een CDN dan maak je nog steeds gebruik van dat CDN toch? Dus dat de data niet 'door' je Laravel instance loopt.
Nee, maar ik bedoel dit anders. :)

Of de bestanden nu op een CDN staan of op je eigen opslagmedium, het moet door de applicatie verwerkt worden. Ik gaf alleen aan Laravel + Vue te gebruiken.

  • Hydra
  • Registratie: September 2000
  • Laatst online: 21-08 17:09
HollowGamer schreef op donderdag 19 september 2019 @ 10:30:
Of de bestanden nu op een CDN staan of op je eigen opslagmedium, het moet door de applicatie verwerkt worden.
Nou nee, da's het punt. Je applicatie doet gewoon een redirect dus die data gaat niet door je applicatie heen. Dat zou een CDN nogal nutteloos maken als het alsnog door die ene service van jou gaat.

https://niels.nu


  • Marco1994
  • Registratie: Juli 2012
  • Laatst online: 17:41
HollowGamer schreef op donderdag 19 september 2019 @ 09:12:
@Marco1994 JWT werkt prima, enkel doe ik alles met Vue/JS, dus moet ik ook alles afhandelen in die laag.

Het gaat dus niet om het streamen zelf, maar meer hoe je tokens verwerkt bij het ophalen van data/media.
Als JWT al werkt, dan moet het toch niet zo moeilijk zijn om in je Vue/js laag, die token met elke request mee te sturen. als je een request doet naar https://api.example.com/getFile, checkt die controller je token en als dat allemaal okay is. Krijg je een file terug, als het niet okay is krijg je een 401/403 terug

  • ThomasG
  • Registratie: Juni 2006
  • Laatst online: 23-09 14:00
Een gangbare manier is om gebruik te maken van een token (die tijdelijk geldig is), dat door de applicatie wordt gegenereerd. Voor iedere asset waar de gebruiker toegang to heeft wordt een token gegenereerd; het CDN kan deze token valideren. Het CDN hoeft dan niets te weten van authenticatie, de gebruikers, e.d. want het CDN kan zelf berekenen of het token hoort bij dat bestand en dat het nog niet verlopen is.

Bij de meeste CDN's zijn hier gewoon bestaande oplossingen voor wat je met een paar regeltjes code kunt inbouwen. Zoals bij Cloudflare:
PHP:
1
2
3
4
5
6
7
<?php
// Generate valid URL token
$secret = "thisisasharedsecret";
$time   = time();
$token  = $time . "-" . urlencode(base64_encode(hash_hmac("sha256", "/download/private.jpg$time", $secret, true)));
$url    = "http://www.domain.com/download/private.jpg?verify=" . $token;
?>


Edit: je zou zoiets ook eenvoudig zelf kunnen bouwen met een interceptor/middleware dat de tokens valideert.

[ Voor 5% gewijzigd door ThomasG op 19-09-2019 10:45 ]


  • Batavia
  • Registratie: Mei 2011
  • Laatst online: 18:49
Ik zet ook vaak bestanden gewoon in een database (blob velden) die je dan kunt opvragen via de normale rest api. Als het geen grote video's zijn is dit goed te doen.

Bijkomend voordeel is dat als gebruikers deze bestanden uploaden je minder gevoelig bent met mogelijk gerommel op je filesysteem ergens. (omdat iemand iets met een virus upload, wat natuurlijk niet wilt zeggen dat je niet op virrussen moet scannen, alleen mijn server is er minder gevoelig voor)

Je houd dan maximale controle

[ Voor 28% gewijzigd door Batavia op 19-09-2019 11:15 ]


  • Marco1994
  • Registratie: Juli 2012
  • Laatst online: 17:41
peltco schreef op donderdag 19 september 2019 @ 11:12:
Ik zet ook vaak bestanden gewoon in een database die je dan kunt opvragen via de normale rest api.

Bijkomend voordeel is dat als gebruikers deze bestanden uploaden je minder gevoelig bent met mogelijk gerommel op je filesysteem ergens. (omdat iemand iets met een virus upload, wat natuurlijk niet wilt zeggen dat je niet op virrussen moet scannen, alleen mijn server is er minder gevoelig voor)

Je houd dan maximale controle
Waarom zou je ervoor kiezen om bestanden op te slaan in de database? Als er al iemand in het lokale bestand systeem van de server kan rondneuzen, kunnen ze toch ook al bij de database. Een normale database is niet gemaakt voor dat soort data, gebruik het er dan ook niet voor.

  • n9iels
  • Registratie: November 2017
  • Niet online
peltco schreef op donderdag 19 september 2019 @ 11:12:
Ik zet ook vaak bestanden gewoon in een database (blob velden) die je dan kunt opvragen via de normale rest api. Als het geen grote video's zijn is dit goed te doen.

Bijkomend voordeel is dat als gebruikers deze bestanden uploaden je minder gevoelig bent met mogelijk gerommel op je filesysteem ergens. (omdat iemand iets met een virus upload, wat natuurlijk niet wilt zeggen dat je niet op virrussen moet scannen, alleen mijn server is er minder gevoelig voor)

Je houd dan maximale controle
Dat raad ik om meerdere redenen af. Voornamelijk omdat je database daar strik gezien niet voor is gemaakt en dus niet voor is geoptimaliseerd. Een klein bestand van < 1MB is, zoals je zelf aangeeft, geen extreem groot probleem. Maar bij grote bestanden belast je je database onnodig en is je response tijd sowieso langer dan via het normale filesysteem. Daarnaast verlies je ook de voordelen van eventuele RAM/disk caching van het filesystem

edit: De excact bestandsgrote van een "groot" en "klein" bestand verschilt met database

[ Voor 7% gewijzigd door n9iels op 19-09-2019 11:37 ]


  • DataGhost
  • Registratie: Augustus 2003
  • Laatst online: 03-10 23:11

DataGhost

iPL dev

peltco schreef op donderdag 19 september 2019 @ 11:12:
Ik zet ook vaak bestanden gewoon in een database (blob velden) die je dan kunt opvragen via de normale rest api. Als het geen grote video's zijn is dit goed te doen.

Bijkomend voordeel is dat als gebruikers deze bestanden uploaden je minder gevoelig bent met mogelijk gerommel op je filesysteem ergens. (omdat iemand iets met een virus upload, wat natuurlijk niet wilt zeggen dat je niet op virrussen moet scannen, alleen mijn server is er minder gevoelig voor)

Je houd dan maximale controle
Je bestanden komen hopelijk identiek aan de input terug uit de database, of het nou plaatjes of virussen zijn, dus dat argument gaat niet op. En so what als mensen duizenden virussen uploaden in je filesystem, het is niet alsof ze magischerwijs worden uitgevoerd, die blijven daar lekker staan en er gebeurt helemaal niks mee. Het enige wat je nou hebt bereikt is dat je database in theorie een stuk langzamer is geworden (in de praktijk zal het meevallen want aan je oplossing te zien draait je site geen spannende hoeveelheid traffic), en afhankelijk van welke database je kiest (ik heb zo'n vermoeden) heb je een grote kans dat het opschonen van oude bestanden (bijv. als je diskspace opraakt) de database niet verkleint zonder dat jij handmatig door hoepels (met downtime) springt. En dat laatste wordt helemaal leuk als je geen fatsoenlijke size checks hebt, dan kan iemand je gewoon proberen te DoSsen door je database vol te stampen met grote bestanden of anders een hoop kleintjes.

Verder is het al dan niet gebruiken van een database niet een antwoord op wat de TS vraagt.

  • HollowGamer
  • Registratie: Februari 2009
  • Niet online
Marco1994 schreef op donderdag 19 september 2019 @ 10:40:
[...]


Als JWT al werkt, dan moet het toch niet zo moeilijk zijn om in je Vue/js laag, die token met elke request mee te sturen. als je een request doet naar https://api.example.com/getFile, checkt die controller je token en als dat allemaal okay is. Krijg je een file terug, als het niet okay is krijg je een 401/403 terug
Dat is dus ook wat wil en bedoel.. maar hoe je doe je zoiets (goed)? :)
Nu stuur ik deze dus in base64 vorm naar de client, maar dat is onbruikbaar voor grote(re) bestanden.
Kan je bijvoorbeeld met iets als axios streamen naar de client? Het is misschien belangrijk om te weten dat bij mij de backend en frontend gescheiden zijn van elkaar. Als dit niet zo was geweest, dat was het denk ik iets eenvoudiger geweest omdat je dit direct kan doen zonder een extra tussenlaag van authenticatie.
ThomasG schreef op donderdag 19 september 2019 @ 10:42:
Een gangbare manier is om gebruik te maken van een token (die tijdelijk geldig is), dat door de applicatie wordt gegenereerd. Voor iedere asset waar de gebruiker toegang to heeft wordt een token gegenereerd; het CDN kan deze token valideren. Het CDN hoeft dan niets te weten van authenticatie, de gebruikers, e.d. want het CDN kan zelf berekenen of het token hoort bij dat bestand en dat het nog niet verlopen is.

Bij de meeste CDN's zijn hier gewoon bestaande oplossingen voor wat je met een paar regeltjes code kunt inbouwen. Zoals bij Cloudflare:
PHP:
1
2
3
4
5
6
7
<?php
// Generate valid URL token
$secret = "thisisasharedsecret";
$time   = time();
$token  = $time . "-" . urlencode(base64_encode(hash_hmac("sha256", "/download/private.jpg$time", $secret, true)));
$url    = "http://www.domain.com/download/private.jpg?verify=" . $token;
?>


Edit: je zou zoiets ook eenvoudig zelf kunnen bouwen met een interceptor/middleware dat de tokens valideert.
Thanks voor je voorbeeld. :)

Aan zoiets was ik inderdaad aan het denken (zie TS), maar vroeg mij af of dit gebruikelijk was en/of misschien beter kon. Het nadeel lijkt me ook dat je die linkt kunt onderscheppen/doorgeven en dus er zo toch bijkomt, al kun je dat bv. controleren op IP/agent match met de eerste opener?
peltco schreef op donderdag 19 september 2019 @ 11:12:
Ik zet ook vaak bestanden gewoon in een database (blob velden) die je dan kunt opvragen via de normale rest api. Als het geen grote video's zijn is dit goed te doen.

Bijkomend voordeel is dat als gebruikers deze bestanden uploaden je minder gevoelig bent met mogelijk gerommel op je filesysteem ergens. (omdat iemand iets met een virus upload, wat natuurlijk niet wilt zeggen dat je niet op virrussen moet scannen, alleen mijn server is er minder gevoelig voor)

Je houd dan maximale controle
Dat is helaas niet te doen. Een tijd geleden deed ik bijvoorbeeld ook thumbnails storen als blob, maar zelfs die zorgde er al snel voor dat een database ontzettend groot/log werd naar verloop van tijd. In mijn optiek horen die ook gewoon in hun normale vorm te blijven zeg maar. ;)

  • Marco1994
  • Registratie: Juli 2012
  • Laatst online: 17:41
@HollowGamer Dus als ik je goed begrip, is je vraag niet hoe je de authenticatie werkend moet krijgen. Maar hoe je het bestand naar de client krijgt? In dat geval heeft @DJMaze an antwoord gegeven op je vraag.

  • HollowGamer
  • Registratie: Februari 2009
  • Niet online
Marco1994 schreef op donderdag 19 september 2019 @ 11:55:
@HollowGamer Dus als ik je goed begrip, is je vraag niet hoe je de authenticatie werkend moet krijgen. Maar hoe je het bestand naar de client krijgt? In dat geval heeft @DJMaze an antwoord gegeven op je vraag.
Sorry voor de verwarring, de vraag is dus wel hoe ik dit moet doen met authenticatie. Daarbij neem ik ook het sturen naar de client toe, omdat ik niet weet hoe je dit (het beste) samen moet doen. :)

  • Marco1994
  • Registratie: Juli 2012
  • Laatst online: 17:41
Dat is toch al vaker beantwoord, of via een cdn of dmv tokent (JWT). bij een cdn, krijg je netjes een bestand terug. Als je het zelf wilt doen, kun je gaan voor de optie van djmaze

Acties:
  • +1 Henk 'm!

  • DataGhost
  • Registratie: Augustus 2003
  • Laatst online: 03-10 23:11

DataGhost

iPL dev

HollowGamer schreef op donderdag 19 september 2019 @ 11:48:
[...]

Dat is dus ook wat wil en bedoel.. maar hoe je doe je zoiets (goed)? :)
Nu stuur ik deze dus in base64 vorm naar de client, maar dat is onbruikbaar voor grote(re) bestanden.
Je bedoelt dat je de plaatjes als base64 embed in de html zoals dit?
code:
1
<img src="data:image/jpeg;base64,..." />

Je wilt naar het "oude"
code:
1
<img src="https://example.com/getfile.php?file=xxx&token=yyy&overig=zzz" />

of natuurlijk een ander "soort" URL, maar het idee erachter is dat je in code controleert of degene die het wil zien dat ook mag, en zo ja, het bestand opent en doorstuurt naar de client.
Aan zoiets was ik inderdaad aan het denken (zie TS), maar vroeg mij af of dit gebruikelijk was en/of misschien beter kon. Het nadeel lijkt me ook dat je die linkt kunt onderscheppen/doorgeven en dus er zo toch bijkomt, al kun je dat bv. controleren op IP/agent match met de eerste opener?
Daar is het token voor, of op welke manier je dat ook doet. Je zegt iets als "deze client heeft x minuten toegang tot bestand y evt met token z" tegen je backend en controleert dat daar. Als het bestand eenmaal is opgevraagd zou je het token al kunnen invalidaten, of je kan gewoon voor een geldigheidsperiode van een paar minuten zorgen. Enkel een token is daarbij het meest "robuust" maar daarmee heb je de meeste kans dat de link "gedeeld" wordt, hoewel die waarschijnlijk maar een paar minuten werkt dus het nut beperkt is. Als je inderdaad op IP, agent, actieve sessie, ingelogde gebruiker of andere kenmerken controleert timmer je het verder dicht maar zou het in sommige situaties minder goed kunnen werken (bijv. Tor-verkeer als je op IP controleert).

  • HollowGamer
  • Registratie: Februari 2009
  • Niet online
Marco1994 schreef op donderdag 19 september 2019 @ 11:59:
Dat is toch al vaker beantwoord, of via een cdn of dmv tokent (JWT). bij een cdn, krijg je netjes een bestand terug. Als je het zelf wilt doen, kun je gaan voor de optie van djmaze
Ik denk dat ik mijn vraag niet goed heb opgesteld. Wat @DJMaze lukt mij nu al, dat doe ik nu al met Nginx, maar hoe verwerk je die token dan daarin?

Ik heb nu het volgende: https://example.com/api/assets/1 (zonder auth) - dit geeft je de content terug, maar dus ook voor mensen die niet zijn ingelogd of hier niet de juiste rechten voor hebben.
Wil je dit doen met authenticatie dat moet je die token meesturen (in de header/als query), dat is opzicht geen probleem, het gaat om de combinatie daarvan. Als query (?token=<key>) is het makkelijkst, maar vind ik wel omslachtig en zoals @DataGhost aangeeft, moet ik dan extra checks doen - niet perse erg hoor, maar vroeg mij af of dit de beste oplossing is. :)

Als je even naar mijn TS kijkt, zie je dat dit kan met axios en base64, maar dat is mij nog niet echt gelukt. Verder twijfel ik eraan of dit wel de juiste oplossing is, bij grote bestanden is dat niet te doen lijkt me.

[ Voor 13% gewijzigd door HollowGamer op 19-09-2019 12:11 ]


Acties:
  • Beste antwoord
  • +1 Henk 'm!

  • DataGhost
  • Registratie: Augustus 2003
  • Laatst online: 03-10 23:11

DataGhost

iPL dev

Maar kijk nou eens goed naar de verschillen

https://example.com/api/assets/1:
• Kijk welk bestand hoort bij asset 1
• Open bestand
• Stuur naar client

https://example.com/api/assets/1?token=x:
• Kijk of het token geldig is
• Kijk welk bestand hoort bij asset 1
• Open bestand
• Stuur naar client

Axios/base64:
• Kijk of de client het request mag doen (ik ken axios verder niet)
• Kijk welk bestand hoort bij asset 1
• Open bestand
• Base64 encode bestand
• Stuur naar client (embedded in de pagina die ze openen)

Ik zou het wel weten.
HollowGamer schreef op donderdag 19 september 2019 @ 12:08:
[...]

Als query (?token=<key>) is het makkelijkst, maar vind ik wel omslachtig en zoals @DataGhost aangeeft, moet ik dan extra checks doen - niet perse erg hoor, maar vroeg mij af of dit de beste oplossing is. :)
HollowGamer schreef op woensdag 18 september 2019 @ 20:35:
@Marco1994 Kan je een voorbeeld even hoe je die bestanden serveert? Ik doe ook alles over HTTPS, maar die zijn dus nu zonder authenticatie. Ik wil juist dat iemand moet zijn ingelogd om erbij te komen. Dat gaat opzicht prima met native templates/blades, maar ik gebruik die dus niet en doe alles met Vue.
Hoe was je van plan authenticatie af te dwingen zonder checks?

[ Voor 42% gewijzigd door DataGhost op 19-09-2019 12:19 ]


Acties:
  • +1 Henk 'm!

  • DJMaze
  • Registratie: Juni 2002
  • Niet online
HollowGamer schreef op donderdag 19 september 2019 @ 12:08:
Ik denk dat ik mijn vraag niet goed heb opgesteld. Wat @DJMaze lukt mij nu al, dat doe ik nu al met Nginx, maar hoe verwerk je die token dan daarin?
.....
Verder twijfel ik eraan of dit wel de juiste oplossing is, bij grote bestanden is dat niet te doen lijkt me.
Dan moet je Nginx aanpassen zodat jouw authenticatie systeem daarin zit.

Lukt het je niet om een custom (PAM of SASL) auth module te bouwen die werkt in Nginx/Apache/etc., dan kom je toch weer terug op mijn eerste antwoord.

P.S. Je zit vast in je denkwijze. Je blijft herhalen dat, wat ik zeg, al in Nginx zit.
Stap even uit je denken en bekijk vanuit een helikopter wat jij mist en waarom men mijn antwoord als oplossing geeft.
Want mijn oplossing is namelijk niet de hele oplossing, jij moet dat namelijk zelf doen.
Vandaar dat ik je nu op het zijspoor van PAM/SASL zet.
Want zeg nou zelf: is een PAM/SASL module schrijven makkelijker/moeilijker dan PHP voor jou?

Voor mijn part schrijf je alles in Python/Node.js/.NET/whateffa, zolang de authenticatie en "range" maar samenwerkt.

[ Voor 50% gewijzigd door DJMaze op 19-09-2019 13:22 ]

Maak je niet druk, dat doet de compressor maar


  • HollowGamer
  • Registratie: Februari 2009
  • Niet online
DataGhost schreef op donderdag 19 september 2019 @ 12:15:
[..]

Hoe was je van plan authenticatie af te dwingen zonder checks?
Dat doet nu JWT voor mij, maar hier komt dus iets extra's bij zeg maar. Opzicht niet erg.

Acties:
  • +1 Henk 'm!

  • veltnet
  • Registratie: Mei 2004
  • Laatst online: 30-09 08:29
peltco schreef op donderdag 19 september 2019 @ 11:12:
Ik zet ook vaak bestanden gewoon in een database (blob velden) die je dan kunt opvragen via de normale rest api. Als het geen grote video's zijn is dit goed te doen.
Dit is een enorme bad-practice. Blobs in database zouden verboden moeten worden.

Je kunt je bestanden dan beter buiten de webroot op het filesysteem plaatsen (zijn ze niet direct toegankelijk) en in de database een referentie opnemen naar het bestand. Het bestand wordt naar de gebruiker gestuurd via een PHP script dat eerst de credentials controleert.

  • DJMaze
  • Registratie: Juni 2002
  • Niet online

Maak je niet druk, dat doet de compressor maar


  • TheNephilim
  • Registratie: September 2005
  • Laatst online: 02-10 09:22

TheNephilim

Wtfuzzle

Volgens mij hoef je maar twee dingen te doen:

- In app/filesystems.php een nieuwe disk aanmaken, private in plaats van public. Bestanden komen dan in storage terecht en niet in de public directory
- Een route + controller, met de auth:api (ofwel je jwt auth) middleware, die het bestand met de juiste headers teruggeeft.

Op deze manier kun je gewoon een linkje naar het bestand gebruiken, buiten axios om.

Acties:
  • +1 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 21-08 17:09
Ik snap werkelijk waar niet waarom je in hemelsnaam in Nginx wil gaan lopen kutten. Je wil voor grote shit helemaal geen passthroughs doen (dus: storage -> jouw webserver -> client). Je hebt dan twee keer verkeer in je server; inbound van de storage en outbound naar de client. Als je het lokaal op de HD hebt is er in principe geen dataverkeer, maar dat schaalt niet.

Als je het wel op een CDN heb staan (Amazon, google, whatever), heb je het voordeel dat een CDN zelf zorgt dat het opgeslagen staat dicht bij een client (je Netflix series komen ook niet uit amerika overgevlogen).

Wat je wel wil is, net zoals Netflix dat bijvoorbeeld dat doet, een tijdelijke token berekenen voor een CDN en dan de client via een 302 redirect doorsturen naar de file op de CDN met de hash in de querystring. De CDN verifieert dan de hash. Mag iemand erbij dan krijgt 'ie de data. Mag het niet dan krijgt 'ie gewoon een 403 ofzo.

Als je aan de andere kant gewoon die zooi lokaal wil hebben; doe gewoon lekker een passthrough in je PHP code. Op die schaal maakt het dan toch geen fluit uit.

https://niels.nu


  • Batavia
  • Registratie: Mei 2011
  • Laatst online: 18:49
DataGhost schreef op donderdag 19 september 2019 @ 11:33:
[...]

Je bestanden komen hopelijk identiek aan de input terug uit de database, of het nou plaatjes of virussen zijn, dus dat argument gaat niet op. En so what als mensen duizenden virussen uploaden in je filesystem, het is niet alsof ze magischerwijs worden uitgevoerd, die blijven daar lekker staan en er gebeurt helemaal niks mee. Het enige wat je nou hebt bereikt is dat je database in theorie een stuk langzamer is geworden (in de praktijk zal het meevallen want aan je oplossing te zien draait je site geen spannende hoeveelheid traffic), en afhankelijk van welke database je kiest (ik heb zo'n vermoeden) heb je een grote kans dat het opschonen van oude bestanden (bijv. als je diskspace opraakt) de database niet verkleint zonder dat jij handmatig door hoepels (met downtime) springt. En dat laatste wordt helemaal leuk als je geen fatsoenlijke size checks hebt, dan kan iemand je gewoon proberen te DoSsen door je database vol te stampen met grote bestanden of anders een hoop kleintjes.

Verder is het al dan niet gebruiken van een database niet een antwoord op wat de TS vraagt.
Ik denk dat het precies is wat TS vraagt (of een alternatief op de cdn oplossing in iedergeval). Want met een gewone api kan je authenticatie 'makkelijk' toepassen.

Ik heb dit gedaan voor applicaties met duizenden documenten zonder problemen. Natuurlijk kan het zijn dat sommige databases hier minder goed mee om gaan. met MSSQL heb ik geen problemen ondervonden.

En ik denk dat ook van een virus perspectief er een voordeel is als het gescheiden is van de rest van mijn applicatie. En ook met filesysteem moet je size checks instellen anders kan ook je filesysteem vollopen.

Maar er zijn absoluut ook nadelen aan bestanden in je database zetten.

Nog een alternatief is je applicatie het bestand van disk inlezen en de bestanden niet-toegangelijk ergens neer zetten. Dan kan je ook authenticatie doen op de zelfde manier als de rest van je applicatie

  • DataGhost
  • Registratie: Augustus 2003
  • Laatst online: 03-10 23:11

DataGhost

iPL dev

peltco schreef op donderdag 19 september 2019 @ 14:45:
[...]


Ik denk dat het precies is wat TS vraagt (of een alternatief op de cdn oplossing in iedergeval). Want met een gewone api kan je authenticatie 'makkelijk' toepassen.
Nu is het opeens een API geworden? Net was het nog een database. De storage backend maakt geen hol uit voor het serveren van bestanden na authenticatie, voor mijn part staan ze op een casettebandje, er moet iets gebeuren *tussen* de storage en de client dus het veranderen van de storage is niet de vraag/oplossing. Je zorgt dus dat je een CDN hebt wat dit ondersteunt (of je leest de posts van de TS en ziet dat het om een hobbyproject gaat dus alles lekker lokaal staat), of je doet zelf een passthrough na authenticatie.
Ik heb dit gedaan voor applicaties met duizenden documenten zonder problemen. Natuurlijk kan het zijn dat sommige databases hier minder goed mee om gaan. met MSSQL heb ik geen problemen ondervonden.
Het kan, ja. Uiteindelijk wordt de database ook op een filesystem weggeschreven. Sterker nog, een filesystem *is* een soort database, maar geen relationele. Een relationele database is niet bedoeld om bestanden in op te slaan. Er is geen enkel voordeel om het in een database te doen. Je zorgt eigenlijk enkel voor meer geheugengebruik omdat databaserijen doorgaans alleen maar geheel (dus niet per rij in chunks gesplitst) verwerkt kunnen worden, je houdt een databaseverbinding bezet zolang de transfer duurt en je kan de transfer niet offloaden naar het OS met iets als sendfile. Queries op overige data stall je daar ook mee. Je databasecache is na een paar leuke bestanden ook nutteloos geworden dus daar kakt nog meer performance in. Je zet een database voor hoge IOPS op SSDs en je gebruikt HDDs voor big storage, dat kan je met jouw oplossing ook niet echt lekker doen.
En ik denk dat ook van een virus perspectief er een voordeel is als het gescheiden is van de rest van mijn applicatie.
Noem eens een concreet geval dan, in plaats van "ik denk dat het er is"? Je flikkert alles wat wordt geupload in een niet-world-accessible map (want je wilt authenticatie voor downloaden) en het enige wat je applicatie doet is daarin schrijven (upload opslaan) of lezen (naar client sturen). Nergens komt daar uitvoeren aan bod. Als dat wel zo is heb je of ergens een paar gigantische fouten gemaakt met "niet-world-accessible" of is je database-oplossing net zo kwetsbaar.
En ook met filesysteem moet je size checks instellen anders kan ook je filesysteem vollopen.
Je gebruikt MSSQL, dus wie weet valt het mee. Ik bedoelde concreet MySQL/MariaDB met InnoDB en dan worst-case de default situatie waarin geen file-per-table gebruikt wordt. Er is dan 1 groot bestand waarin je *hele* database komt te staan. Als je daarin 10 jaar aan bestanden hebt staan voor een totaal van 5 TB, je disk raakt vol en je besluit dat je alles ouder dan 7 jaar niet meer nodig hebt, dan kan je lekker die 1.5TB eruit gooien maar het on-disk bestand wordt niet kleiner. De enige manier om dat te fixen is een dump van je database te maken (naar een andere drive/machine met minimaal 3.5TB), je database down te halen, de datafile weg te gooien, je database opnieuw te starten en de hele rotzooi weer te importeren. Zelfs met 1 GB/s (grote B ) ben je dan in het beste geval minimaal 2 uur down. Het ging dus niet zozeer om het vollopen an sich, maar om het recoveren daarvan als het toch gebeurt. Op je filesystem gooi je gewoon wat bestandjes weg.
Nog een alternatief is je applicatie het bestand van disk inlezen en de bestanden niet-toegangelijk ergens neer zetten. Dan kan je ook authenticatie doen op de zelfde manier als de rest van je applicatie
Dat is dus precies wat er in dit topic gezegd wordt.

  • Hydra
  • Registratie: September 2000
  • Laatst online: 21-08 17:09
DataGhost schreef op donderdag 19 september 2019 @ 15:55:
Je gebruikt MSSQL, dus wie weet valt het mee. Ik bedoelde concreet MySQL/MariaDB met InnoDB en dan worst-case de default situatie waarin geen file-per-table gebruikt wordt. Er is dan 1 groot bestand waarin je *hele* database komt te staan.
'Echte' databases slaan grote blobs ook gewoon apart op het FS op en niet in de tabel. Dus als MYSQL dat wel doet, dan is dat eerder een "gebruik geen MySQL" dingetje dan een "sla geen files in je database op" dingetje.

Voor mij is de voornaamste reden dit niet te doen omdat het lastig maakt met back-ups te werken (een file-system rsyncen is peanuts, een terabyte grote DB dump inlezen is wat lastiger), niet dat databases er perse niet mee om kunnen gaan. Maar ik heb dan ook in tijden geen MySQL meer gebruikt.

Afgezien van dat heb ik niet het idee dat het voor de TS ook maar iets uitmaakt aangezien hij sowieso niet voor de simpele schaalbare optie (het overlaten aan een CDN) gaat.

https://niels.nu


Acties:
  • +1 Henk 'm!

  • Sorcix
  • Registratie: Maart 2006
  • Niet online
Gezien je nginx gebruikt: daarvoor bestaan speciale modules:
https://www.nginx.com/res.../topics/examples/x-accel/

In 't kort: je eigen applicatie regelt de authenticatie en geeft als respons een speciale HTTP header met het pad naar de file. Als nginx die header ziet zal die de file in de header serveren als elke andere statische file. Op die manier houd je de beste performance zonder dat je zelf bezig moet zijn met het serveren van statische files.

Het pad dat je meegeeft aan nginx mag buiten je document root staan, zodat de file niet via normale requests bereikbaar is.

[ Voor 11% gewijzigd door Sorcix op 19-09-2019 16:06 . Reden: Extra info over document root. ]


  • Batavia
  • Registratie: Mei 2011
  • Laatst online: 18:49
DataGhost schreef op donderdag 19 september 2019 @ 15:55:
[...]

Nu is het opeens een API geworden? Net was het nog een database. De storage backend maakt geen hol uit voor het serveren van bestanden na authenticatie, voor mijn part staan ze op een casettebandje, er moet iets gebeuren *tussen* de storage en de client dus het veranderen van de storage is niet de vraag/oplossing. Je zorgt dus dat je een CDN hebt wat dit ondersteunt (of je leest de posts van de TS en ziet dat het om een hobbyproject gaat dus alles lekker lokaal staat), of je doet zelf een passthrough na authenticatie.
Serieus? als het in je normale database staat gebruik je toch ook je normale api. Ik heb mijn database niet vrij querybaar vanaf het internet. En ja ik dacht dat mijn oorspronkelijke oplossing duidelijk was maar blijjkbaar niet voor sommige.

Als je files (zoals alle andere data) in je database dan werkt authenticatie ook op de normale manier. Je hoeft dan ook geen CDN te hebben. Zoals TS aangaf dit werkte voor thumbnails niet, prima. Maar als het een hobby project is zou dit het eerste zijn wat ik probeer voordat ik weer wat er bij haal zoals een CDN.

  • DataGhost
  • Registratie: Augustus 2003
  • Laatst online: 03-10 23:11

DataGhost

iPL dev

peltco schreef op donderdag 19 september 2019 @ 16:32:
[...]


Serieus? als het in je normale database staat gebruik je toch ook je normale api. Ik heb mijn database niet vrij querybaar vanaf het internet. En ja ik dacht dat mijn oorspronkelijke oplossing duidelijk was maar blijjkbaar niet voor sommige.

Als je files (zoals alle andere data) in je database dan werkt authenticatie ook op de normale manier. Je hoeft dan ook geen CDN te hebben. Zoals TS aangaf dit werkte voor thumbnails niet, prima. Maar als het een hobby project is zou dit het eerste zijn wat ik probeer voordat ik weer wat er bij haal zoals een CDN.
Sorry, maar nu ben je gewoon om je eigen onkunde heen aan het lullen. Authenticatie heeft in principe helemaal niks met je database te maken en is op heel veel verschillende manieren mogelijk. Mocht je daar een database voor gebruiken wil dat nog niet zeggen dat je niet meer bij het filesystem mag. Met bijv. Facebook-login, DigID of whatever heb je niet eens een database nodig.

Jouw situatie, met files in de database:
https://example.com/asset/1:
• Authenticatie
• Haal asset 1 uit database
• Stuur asset 1 naar client

Met files op het filesystem, niet-world-accessible:
https://example.com/asset/1:
• Authenticatie
• Haal asset 1 uit databasefilesystem
• Stuur asset 1 naar client

NB nog een keer "niet-world-accessible". https://example.com/files/1 werkt dus niet omdat die locatie helemaal niet geserveerd wordt door je webserver.

[ Voor 10% gewijzigd door DataGhost op 19-09-2019 16:47 ]


Acties:
  • +1 Henk 'm!

  • Gomez12
  • Registratie: Maart 2001
  • Laatst online: 17-10-2023
peltco schreef op donderdag 19 september 2019 @ 16:32:
[...]
Serieus? als het in je normale database staat gebruik je toch ook je normale api. Ik heb mijn database niet vrij querybaar vanaf het internet.
Weet je zeker dat je database niet vrij querybaar vanaf het internet is? Dat is namelijk wel exact wat je steeds zegt.

Of je hebt (bij een dbase) 1 authenticatie module en dan is dus je database rechtstreeks aanspreekbaar vanaf het internet.
Of je hebt 2 authenticatie modules, 1 voor web naar je scripting/programmeertaal en 1 voor scripting/programmeertaal naar je database.
En dat is ook exact waarom jouw oplossing irrelevant is bij deze vraag. TS zoekt iets voor de authenticatie van web naar scripting/programmeertaal, en die heb je altijd ongeacht of je er een dbase of niet achter hebt hangen. Met een dbase introduceer je enkel maar een 2e authenticatielaag wat niets bijdraagt voor de TS aangezien zijn probleem zit bij de 1e laag.

En kunnen we nu ophouden over dbases aangezien het niets met de vraag van de TS te maken heeft, en alhoewel er argumenten te maken zijn voor het opslaan in een dbase slaan jouw argumenten echt nergens op.

Acties:
  • 0 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 21-08 17:09
Gomez12 schreef op donderdag 19 september 2019 @ 22:50:
[...]

Weet je zeker dat je database niet vrij querybaar vanaf het internet is? Dat is namelijk wel exact wat je steeds zegt.
Ik ben het ook niet eens met het idee die files maar in de DB te proppen maar ik zie nergens waar 'ie dat zegt.
En kunnen we nu ophouden over dbases aangezien het niets met de vraag van de TS te maken heeft, en alhoewel er argumenten te maken zijn voor het opslaan in een dbase slaan jouw argumenten echt nergens op.
Dat sowieso.

[ Voor 25% gewijzigd door Hydra op 20-09-2019 07:41 ]

https://niels.nu


Acties:
  • 0 Henk 'm!

  • kwaakvaak_v2
  • Registratie: Juni 2009
  • Laatst online: 02-06 12:29
Hydra schreef op donderdag 19 september 2019 @ 14:04:
......
Wat je wel wil is, net zoals Netflix dat bijvoorbeeld dat doet, een tijdelijke token berekenen voor een CDN en dan de client via een 302 redirect doorsturen naar de file op de CDN met de hash in de querystring. De CDN verifieert dan de hash. Mag iemand erbij dan krijgt 'ie de data. Mag het niet dan krijgt 'ie gewoon een 403 ofzo.
...
Dit is eigenlijk de enige goede oplossing. Werk met tijdelijke acces code's voor een URL. Laravel kan dat uit de doos met signed URL's, die onderwater niet meer zijn dan een hash van de querystring met een timestamp gesalt met de APP Key. Nadeel is wel dat die alleen voor routes zijn en niet voor bestanden waar de webserver direct bij kan. Wil je het dus bij Laravel houden ontkom je niet aan om een passthrough constructie via PHP.

Heb je wat meer budget, dan zou je de assets naar een Amazon S3 bucket kunnen uploaden, die heeft een zelfde soort systeem ingebouwd via tijdelijke acces code's die via de API kan aanmaken.

Maar hou het simpel, en trap niet in de val van het over-engineering waar in dit draadje al meer mensen ingevallen zijn ;)

Driving a cadillac in a fool's parade.


Acties:
  • 0 Henk 'm!

  • DJMaze
  • Registratie: Juni 2002
  • Niet online
kwaakvaak_v2 schreef op vrijdag 20 september 2019 @ 10:00:
Wil je het dus bij Laravel houden ontkom je niet aan om een passthrough constructie via PHP.
Een simpele passthru($filepointer) werkt meestal wel.
Maar je moet wel de Range header implementeren, zoals ik zei, als je klagende mensen hebt dat het downloaden van grote bestanden mislukt ;)

Maak je niet druk, dat doet de compressor maar


Acties:
  • +1 Henk 'm!

  • HollowGamer
  • Registratie: Februari 2009
  • Niet online
Voorlopig heb ik de volgende oplossing gevonden:
- Wanneer iemand succesvol is ingelogd wordt https://example.com/api/user (AuthController@user) aangesproken en user data teruggeven:
PHP:
1
2
3
4
5
6
7
8
9
10
11
/**
* Get the authenticated User.
*
* @return \Illuminate\Http\JsonResponse
*/
public function user()
{
    event(new HasBeenAuthenticated($this->guard()->user())); // <-- Deze maakt nieuwe API key

    return response()->json(['data' => $this->guard()->user()]); // user data + (nieuwe/bestaande) API key
}


- De gebruiker kan data op blijven vragen zolang de API-key geldig is:
https://example.com/assets/1?token=<api-key>

- Om de assets te renderen gebruik ik X-Accel icm. met nginx in de AssetsController:

PHP:
1
2
3
4
header('Content-Disposition: attachment; filename="'.basename($path));
header("Content-Type: {$type}");
header("X-Assets-Root: {$root}");
header("X-Accel-Redirect: /assets/{$path}");


Daardoor hoef ik zelf niks te doen en gaan zowel dingen als muziek, video en afbeeldingen prima. :)

[ Voor 3% gewijzigd door HollowGamer op 20-09-2019 15:17 ]


Acties:
  • +1 Henk 'm!

  • Voutloos
  • Registratie: Januari 2002
  • Niet online
Klinkt als een juiste oplossing.

En als je X-Accel kan gebruiken is dat inderdaad een fijne. :)

[ Voor 3% gewijzigd door Voutloos op 20-09-2019 15:41 ]

{signature}

Pagina: 1