Uw startup opbouwen mensen uitnodigen via URL

Wat je gaat creëren

Deze zelfstudie maakt deel uit van de Bouw je Startup met PHP-serie op Envato Tuts +. In deze serie begeleid ik je door het opstarten van een startup van concept naar realiteit met behulp van mijn Meeting Planner app als een realistisch voorbeeld. Elke stap die ik doe, zal ik de Meeting Planner-code vrijgeven als open-source voorbeelden waar je van kunt leren. Ik zal ook opstartgerelateerde zakelijke problemen aanpakken zodra deze zich voordoen.

Plan uw groepsbijeenkomst via de snelkoppelings-URL

Welkom! Ik ben onlangs terug van mijn favoriete plek in de wereld, die ik aan het einde van de laatste aflevering heb genoemd. Na het voltooien van meerdere deelnemersbijeenkomsten nam ik een pauze in de natuur.

Vandaag zal ik de mogelijkheid toevoegen om deelnemers aan een vergadering uit te nodigen door een beveiligde URL te delen die aan uw vergadering is gekoppeld. Dit is vooral handig voor het plannen van groepsbijeenkomsten. Als u bijvoorbeeld 30 mensen wilt uitnodigen, is het soms gemakkelijker om een ​​e-mail te verzenden naar iedereen met een uitnodigings-URL.

Als je dit nog niet hebt gedaan, probeer dan vandaag je eigen groepsbijeenkomst te plannen! Nodig een paar vrienden uit om je te ontmoeten voor kombucha, kava of koffie. Deel uw mening en feedback over de ervaringen van iedereen in de opmerkingen hieronder. Ik neem deel aan de discussies, maar je kunt me ook bereiken @reifman op Twitter. Ik sta altijd open voor nieuwe functie-ideeën voor Meeting Planner en suggesties voor toekomstige serie-afleveringen.

Ter herinnering, alle code voor Meeting Planner wordt open source gegeven en geschreven in het Yii2 Framework voor PHP. Als je meer wilt weten over Yii2, bekijk dan mijn parallelle serie Programming With Yii2. 

Voordat ik deze functie onderneem, wil ik enkele voorbeelden geven van enkele veelvoorkomende bugs (of vergissingen) die ik ben tegengekomen bij het bouwen van de service. (Als u alleen wilt lezen over beveiligde deelbare URL's, kunt u dit gedeelte overslaan.)

De Startup Bug Interlude

Naarmate steeds meer mensen Meeting Planner proberen te proberen, komen er bugs binnen. En vaak merk ik ze tijdens de ontwikkeling zelf op. Hier zijn een paar recente, gewoon om je een idee te geven van het startup-leven. 

Het kan moeilijk zijn om je te concentreren op bedrijfsontwikkeling en marketing, nieuwe functies te coderen en bugs te identificeren en op te lossen. De startup van één persoon groeit in moeilijkheden als de functies van uw site groeien.

Zoals ik eerder al schreef, gebruik ik Asana momenteel voor functieplanning, maar ook om fouten bij te houden.

Het If Assignment-probleem

Ik weet zeker dat als ik meer expertise als ontwikkelaar had, met collega's werkte, of meer tijd had om Meeting Planner niet te coderen, ik precies zou weten welke extensie van de Atom Editor hierop zoekt. Als je het weet, plaats het dan in de comments.

Blijkbaar gebruikte ik in een belangrijke functie om te controleren of een kijker daadwerkelijk een vergaderdeelnemer was, een opdracht om te controleren. Met andere woorden, ik vroeg niet of de eigenaar de kijker was - ik maakte dit tijdelijk aan de gang.

public static function isAttendee ($ meeting_id, $ user_id) $ m = Meeting :: findOne ($ meeting_id); // zijn zij de organisator? // EEEK! if ($ m-> owner_id = $ user_id) return true; 

Je herinnert je dat twee gelijken een vergelijking is, één gelijk aan een opdracht. Net als periodes voor aaneenschakelingen en plustekens voor toevoeging, behalve in JavaScript, waar ze voor eindeloos moeilijk te vinden bugs zijn (wat ook de reden is waarom Ajax de hel is in PHP).

Databasequery's die in de loop van de tijd mislukken

Toen mijn eigen gebruik van Meeting Planner toenam, waren er steeds meer vergaderingen in mijn views met tabbladen. En toen merkte ik dat er soms duplicaten zouden verschijnen. Dit was moeilijk eerder te detecteren wanneer er minder gegevens waren.

Mijn tabspecifieke vergaderingquery's (zoals het plannen van een vergadering, bevestigde vergaderingen, eerdere vergaderingen, enz.) Isoleerden geen unieke items:

$ planningProvider = new ActiveDataProvider (['query' => Meeting :: find () -> joinWith ('deelnemers') -> where (['owner_id' => Yii :: $ app-> user-> getId ()] ) -> orWhere (['participant_id' => Yii :: $ app-> user-> getId ()]) -> andWhere (['meeting.status' => [Meeting :: STATUS_PLANNING, Meeting :: STATUS_SENT]] ) / * NEEDED TO ADD THIS * / -> distinct (), 'sort' => ['defaultOrder' => ['created_at' => SORT_DESC]], 'pagination' => ['pageSize' => 7, ' params '=> array_merge ($ _ GET, [' tab '=>' planning ']),],]); 

Het toevoegen -> Afzonderlijke () om de vraag opgelost.

Yii2 paginering op rasterweergaven op tabbladen 

Een andere bug die ik tegenkwam met meer gegevens was dat Yii2-paginakoppelingen me altijd terugvoerde naar het eerste tabblad.

Ik heb een queryparameter voor het huidige tabblad toegevoegd die MeetingController.php actionIndex zoekt nu naar:

public function actionIndex () if (Meeting :: countUserMeetings (Yii :: $ app-> user-> getId ()) == 0) $ this-> redirect (['create']);  $ tab = 'planning'; if (isset (Yii :: $ app-> request-> queryParams ['tab'])) $ tab = Yii :: $ app-> request-> queryParams ['tab'];  $ planningProvider = new ActiveDataProvider (['query' => Meeting :: find () -> joinWith ('deelnemers') -> where (['owner_id' => Yii :: $ app-> user-> getId () ]) -> orWhere ([ 'participant_id' => Yii :: $ app-> gebruiksvriendelijkheid> getId ()]) -> andWhere ([ 'meeting.status' => [Ontmoeting :: STATUS_PLANNING, Ontmoeting :: STATUS_SENT] ]) -> distinct (), 'sort' => ['defaultOrder' => ['created_at' => SORT_DESC]], 'pagination' => ['pageSize' => 7, 'params' => array_merge ($ _GET, ['tab' => 'planning']),],]);

Ook heb ik het pagineringparams om de huidige tabbladinstelling samen te voegen. Wanneer gebruikers op een andere paginalink klikken, wordt het huidige tabblad nu opgenomen.

Ten slotte heb ik ook de /frontend/views/meeting/index.php hiervan bewust gemaakt, door het actieve tabblad in te stellen vanuit de queryparameter:

 
">
render ('_ grid', ['mode' => 'planning', 'dataProvider' => $ planningProvider, 'timezone' => $ timezone,])?>
">
render ('_ grid', ['mode' => 'aankomend', 'dataProvider' => $ upcomingProvider, 'timezone' => $ timezone,])?>
"> render ('_ grid', ['mode' => 'past', 'dataProvider' => $ pastProvider, 'timezone' => $ timezone,])?>

Dit zijn slechts enkele goede voorbeelden van de dagelijkse bugs die ik tegenkom om een ​​startup te bouwen in de scope van Meeting Planner.

Laten we nu eens nadenken over het bouwen van uitnodigingen via snelkoppelings-URL's zoals beloofd.

Veilige deelbare snelkoppelings-URL's bouwen

Denken over beveiliging voor URL's

Om het moeilijker te maken voor een willekeurige explosie van URL-gissingen om in te breken in iemand's uitnodiging voor de vergadering, moest ik een vrij unieke sleutel hebben in combinatie met een onbeschrijfelijke code.

Ik besloot de gebruikersnaam van de persoon als sleutel te gebruiken. Elke gebruiker zou een groot aantal bijna onleesbare vergaderingscodes hebben.

Zo kan een vergadering-URL bijvoorbeeld zijn https://meetingplanner.io/presidenthillary/X1Y2Z3A7C9.

Voor de code heb ik besloten om acht hoofdlettergevoelige alfanumerieke tekens te gebruiken. Met andere woorden, elk teken zou a-z, A-Z of 0-9 zijn, in wezen 62 mogelijkheden voor elk teken.

Het totale aantal mogelijkheden voor elke gebruiker is 218.340.105.584.896-meer dan 218 biljoen. Oh, en je zou de gebruikersnaam van je doelwit moeten weten om te beginnen! Het zou veel gemakkelijker zijn om het e-mailaccount van een deelnemer te hacken.

De beveiligingscode voor elke vergadering toevoegen

Om een ​​beveiligingscode toe te voegen aan alle bestaande vergaderingen, heb ik een migratie gemaakt, m160902_174350_extend_meeting_for_identifier.php:

class m160902_174350_extend_meeting_for_identifier breidt Migration uit public function up () $ tableOptions = null; if ($ this-> db-> driverName === 'mysql') $ tableOptions = 'KARAKTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB';  $ this-> addColumn ('% meeting', 'identifier', Schema :: TYPE_STRING. 'NOT NULL'); $ all = Meeting :: find () -> where (['identifier' => "]) -> all (); foreach ($ all als $ m) $ m-> identifier = Yii :: $ app-> beveiliging-> generateRandomString (8); $ m-> update ();

Je zult merken dat ik in deze migratie eigenlijk code gebruik om willekeurige strings te maken voor elke bestaande vergadering, d.w.z.. Yii :: $ app-> beveiliging-> generateRandomString (8);.

Het komt niet vaak voor dat ik code in een migratie schrijf om bestaande delen van de database bij te werken. In dit geval werkt het probleemloos. Andere keren heb ik het /frontend/models/Fix.php-model gebruikt.

Ook in Ontmoeting :: BeforeSave (), Ik heb geautomatiseerde code toegevoegd om een ​​ID te genereren voor alle toekomstige vergaderingen:

public function beforeSave ($ insert) if (parent :: beforeSave ($ insert)) if ($ insert) $ this-> identifier = Yii :: $ app-> security-> generateRandomString (8);  return true;  

Yii Routing uitbreiden

Hoewel het het gemakkelijkst was om een ​​voorvoegsel voor de controller op te nemen, zoals / M / gebruikersnaam / identiteit code, Ik wilde dat koppelingen eenvoudig zijn, zonder extra prefix. Dit vereiste de verlenging van de Yii Routing. 

Als ik dit binnen zijn eigen model had gehouden voor het voorvoegsel en de gebruikersnaam, had ik mogelijk kunnen gebruiken waar ik over schreef in Yii2 Sluggable Behaviors en Building Your Startup: Geolocation and Google Places.

In plaats daarvan heb ik toegevoegd  '/'=>' vergadering / identiteit ', die elke gebruikersnaam met een identiteitsteken toewijst aan de MeetingController actionIdentity () methode.

'urlManager' => ['class' => 'yii \ web \ UrlManager', 'enablePrettyUrl' => true, 'showScriptName' => false, // 'enableStrictParsing' => false, 'rules' => ['plaats '=>' plaats ',' plaats / jouwe '=>' plaats / jouwe ',' plaats / maak '=>' plaats / maak ',' plaats / create_geo '=>' plaats / create_geo ',' plaats / create_place_google '=>' plaats / create_place_google ',' plaats / bekijk /'=>' plaats / aanzicht ',' plaats / update /'=>' plaats / update ',' plaats /'=>' plaats / naaktslak ','/'=>'/ bekijken ','//'=>'/',' daemon /'=>' daemon /', // incl acht char action' site /'=>' site /', // incl acht char action' features '=>' site / features ',' about '=>' site / about ','/'=>' vergadering / identiteit ', // opmerking - huidige acties met 8 letters en geen params zullen mislukken'/'=>'/',],],

Hiermee kwam ik een paar problemen tegen. Ik moest de regels opnieuw rangschikken en statische routes plaatsen voor acht tekens acties die mogelijk op een gebruikersnaam (in plaats van een controller) en een methode (in plaats van een identiteitssleutel) leken te lijken.

Bijvoorbeeld https://meetingplanner.io/site/features toegewezen aan een gebruiker met de naam een ​​site met een beveiligde vergader-ID van 'features' in plaats van de coole nieuwe feature-tabel van Meeting Planner.

Maar toen ik me eenmaal op de problemen heb gericht, werkte alles goed.

De identiteitsmethode van de vergaderingscontroller

Vervolgens heb ik gebouwd actionIdentity () binnen MeetingController:

 public function actionIdentity () // lijst met pad ophalen ($ gebruikersnaam, $ identifier) ​​= ontploffen ("/", Yii :: $ app-> request-> getPathInfo ()); // verifieer de meeting-identifier $ m = Meeting :: find () -> where (['identifier' => $ identifier]) -> one (); if (is_null ($ m) || ($ m-> owner-> gebruikersnaam! = $ gebruikersnaam)) // toegangsfout return $ this-> redirect (['site / authfailure']);  // identifier is authentiek als (Yii :: $ app-> user-> isGuest) // redir to Participant join form return $ this-> redirect (['/ deelnemer / join', 'meeting_id' => $ m -> id, 'identifier' => $ identifier]);  else $ user_id = Yii :: $ app-> user-> getId (); if (! Meeting :: isAttendee ($ m-> id, $ user_id)) // indien niet een deelnemer - voeg ze toe als deelnemer Deelnemer :: voeg toe ($ m-> id, $ user_id, $ m-> owner_id);  return $ this-> actionView ($ m-> id); 

Ten eerste controleert het dat de gebruikersnaam en identiteit overeenkomen met een bestaande gebruiker en een bestaande vergadering. Zo niet, dan sturen we ze naar authfailure.

Als de gebruiker al is ingelogd, voegen we deze automatisch toe als deelnemer aan deze pagina en leiden ze door naar de weergavepagina van de vergadering.

Als dat niet het geval is, sturen we ze naar een deelnemer-controller. Doe mee aan actie om in te loggen of in te loggen.

Deelnemer Verzoek om deel te nemen aan een vergadering

Laten we bijvoorbeeld zeggen dat ik de volgende e-mailuitnodiging van een vriend ontvang:

https://meetingplanner.io/tomeMcFarline/JzRq1a42. Ik krijg deze pagina te zien:

Als de gebruiker zich via een sociaal netwerk wil aanmelden voor Meeting Planner, kunnen we zijn e-mailadres valideren.

Dus ik heb de Yii-retour-URL ingesteld (een pagina waarnaar de gebruiker wordt doorverwezen na een geslaagde aanmelding of aanmelding) die ze na authenticatie terugstuurt naar de pagina Vergaderingsweergave..

// stel return Url Yii in: $ app-> user-> setReturnUrl ($ m-> getSharingUrl ());

De sociale authenticatie, login en / of aanmelding wordt grotendeels beheerd door de code die ik heb beschreven in Building Your Startup: Onramp vereenvoudigen met OAuth.

Als de deelnemer nieuw is, geven ze hun voor- en achternaam en e-mailadres op. We voegen ze aan de vergadering toe als een niet-geverifieerde deelnemer, vergelijkbaar met wat we doen wanneer een gebruiker iemand uitnodigt door een nieuw e-mailadres aan de vergadering toe te voegen.

$ model = nieuwe deelnemer; $ model-> meeting_id = $ meeting_id; ... $ model-> invited_by = $ m-> owner_id; $ model-> status = Deelnemer :: STATUS_DEFAULT; if (! $ validationError && $ model-> validate ()) $ model-> participant_id = Gebruiker :: addUserFromEmail ($ model-> email); $ Model-> save (); // e-mail opzoeken om te zien of ze bestaan ​​Ontmoeting :: displayNotificationHint ($ meeting_id); $ user = User :: findOne ($ model-> deelnemer_id); Yii :: $ app-> gebruiksvriendelijkheid> login ($ user); return $ this-> redirect (['/ meeting / view', 'id' => $ meeting_id]); 

Je vraagt ​​je nu af, Jeff, wat is er met de ... vandaag? Dit is eenvoudig, toch? We voegen gewoon een nieuwe gebruiker toe aan een vergadering.

Terwijl ik dit codeerde, realiseerde ik me dat ik een enorm gat in de privacy creëerde.

Een leuk veiligheidsgat Voorbeeld

Laten we zeggen dat Tom McFarlin op een dag niets te doen heeft en besluit met mij (en God) te rotzooien. Hij zal een nieuwe vergadering maken en wetende dat de Dalai Lama een regelmatige gebruiker van de vergaderplanner is (vanwege al zijn spirituele bijeenkomsten), zal McFarlin hem toevoegen aan zijn vergadering via de e-mail [email protected].

Dan pakt hij zijn fraaie beveiligde snelkoppelings-URL. Mij ​​volgen?

Vervolgens zal McFarlin een andere browser openen en zijn veilige snelkoppelings-URL openen en doen alsof hij de Dalai Lama is die net een andere uitnodiging heeft ontvangen via e-mail van Tom, d.w.z. hij zal proberen zich bij zijn eigen vergadering aan te sluiten alsof hij de Dalai Lama is. d.w.z. Dalai, Lama, [email protected].

Aanvankelijk nam ik aan dat het onwaarschijnlijk was dat iemand ooit de beveiligde URL zou raden, dus als dat zou gebeuren, liet ik iemand op deze manier inloggen. 

Maar dit zou de gevaarlijke McFarlin toegang geven tot het account van de Dalai Lama (deels omdat ik nog niet de beperkte toegangsmodus heb gebouwd voor gebruikers die binnenkomen via URL's om maar één vergadering te zien totdat ze inloggen).

Ja, mijn initiële code werkte op deze manier. En toen kreeg ik een roep uit de hemel en wees erop. 

Wat als McFarlin Sally en Sally heeft uitgenodigd om de beveiligde URL door te sturen naar Bill Gates? Door Bill Gates handmatig als eerste aan de vergadering toe te voegen, kon McFarlin alle Gates-vergaderingen openen met deze truc.

De nieuwe code vereist dat een deelnemer de beveiligde URL gebruikt die al is toegevoegd aan de vergadering om zich handmatig aan te melden. Hier is de ... code:

if ($ model-> load (Yii :: $ app-> request-> post ())) // vragen doet de persoon die lid wordt al in de User-tabel // kan door de organisator aan de uitnodiging zijn toegevoegd of is mogelijk al aanwezig een geregistreerde gebruiker $ person = Gebruiker :: find () -> where (['email' => $ model-> email]) -> one (); if (! is_null ($ person)) // gebruikers-e-mailadres bestaat al // verbeter hun profiel $ postedVars = Yii :: $ app-> request-> post (); if (! empty ($ postedVars ['Participant'] ['firstname'])) $ model-> firstname = $ placedVars ['Participant'] ['firstname'];  if (! empty ($ postedVars ['Participant'] ['lastname'])) $ model-> lastname = $ placedVars ['Deelnemer'] ['achternaam'];  UserProfile :: verbeteren ($ person-> id, $ model-> voornaam, $ model-> achternaam); // zijn ze een deelnemer als (Meeting :: isAttendee ($ model-> meeting_id, $ person-> id)) / * // om dit te doen - dit moet worden gewijzigd in de modus voor beperkte toegang of $ identity = $ persoon-> findIdentity ($ persoon-> id); Yii :: $ app-> gebruiksvriendelijkheid> login ($ identiteit); // to do - update gebruikersprofiel met voor- en achternaam $ this-> redirect (['meeting / view', 'id' => $ model-> meeting_id]);  else * / // let op - schakel deze vereiste niet uit // een persoon zou een beroemdheid aan een vergadering kunnen toevoegen door hun e-mailadres te gebruiken met elke vergadercode en in te loggen met hun account Yii :: $ app-> getSession () -> setFlash ('warning', Yii :: t ('frontend', 'Aangezien u al een account heeft, kunt u hieronder inloggen.')); return $ this-> redirect (['/ site / login']); 

Ik zal dit opnieuw patchen zodra ik de beperkte toegangsmodus met één vergadering heb gemaakt.

Ik had hier misschien niet over nagedacht als ik niet had geweten hoe slinkend McFarlin is. Oef. Nog een koningin gered van vergiftiging.

Waarschijnlijk nam mijn lange weekend in de natuur me ook meer contact met de hemel.

Wat zit er in de pijplijn?

Openbaar domein via Google & Hawaii Afbeelding van de dag

Ik hoop dat je deze aflevering leuk vond bij het maken van beveiligde URL's om mensen uit te nodigen voor vergaderingen. Ik kan me voorstellen dat het zeer herbruikbaar is voor andere scenario's in je eigen diensten. 

Je kunt waarschijnlijk ook zeggen dat ik geniet van het potentieel dat Meeting Planner op dit moment lijkt te hebben - of dat ik gewoon te lang heb gewerkt.

Uiteindelijk biedt het samenstellen van beveiligde URL-uitnodigingen ook de mogelijkheid om gebruikers een openbare planningspagina aan te bieden. Ik kan bijvoorbeeld mijn openbare Meeting Planner-URL delen met vrienden en me laten plannen https://meetingplanner.io/username.

In de toekomst zou ik Meeting Planner zelfs kunnen uitbreiden met abonnementsfuncties voor professionals om afspraken te maken op hun openbare Meeting Planner-pagina. Ik heb echter andere, meer opwindende ideeën. Dit gebied is goed ingeperkt door andere bedrijfsgerichte bedrijven.

Riding the Wave Home

Als u dat nog niet hebt gedaan, plant u nu uw eerste vergadering met Meeting Planner! Probeer de snelkoppelings-URL van uw vergadering te delen en geheim te houden voor onze redactiegod Tom McFarlin.

Je kunt me ook bereiken via @reifman. Ik sta altijd open voor nieuwe functie-ideeën en suggesties voor onderwerpen voor toekomstige zelfstudies. Of probeer onze helpdesk en open een bugrapport of een kaart voor een verzoek om een ​​verzoek. 

Een tutorial over crowdfunding is ook in de maak, dus volg onze WeFunder Meeting Planner-pagina.

Blijf op de hoogte van dit alles en meer komende tutorials door te kijken naar de Building Your Startup With PHP-serie. 

Gerelateerde Links