Construire votre démarrage Inviter des personnes via une URL

Ce que vous allez créer

Ce tutoriel fait partie de la Construire votre démarrage avec la série PHP sur Envato Tuts +. Dans cette série, je vous guide dans le lancement d’une startup du concept à la réalité en utilisant mes Planificateur de réunion application comme exemple de la vie réelle. À chaque étape du processus, je publierai le code de Meeting Planner sous forme d’exemples open source à partir desquels vous pourrez apprendre. Je traiterai également les problèmes liés au démarrage au fur et à mesure qu'ils surviennent.

Planifiez votre réunion de groupe via une URL de raccourci

Bienvenue! Je viens de rentrer de mon endroit préféré dans le monde, que j'ai mentionné à la fin du dernier épisode. Après avoir complété plusieurs réunions de participants, j'ai pris une pause dans la nature.

Aujourd'hui, j'ajouterai la possibilité d'inviter les participants à la réunion en partageant une URL sécurisée associée à votre réunion. Cela sera particulièrement utile pour planifier des réunions de groupe. Par exemple, si vous souhaitez inviter 30 personnes, il est parfois plus facile de déposer un courrier électronique à tout le monde avec une URL d'invitation..

Si vous ne l'avez pas encore fait, essayez de planifier votre propre réunion de groupe aujourd'hui! Invitez quelques amis à vous rencontrer pour un kombucha, un kava ou un café. Partagez vos impressions et commentaires sur l'expérience de chacun dans les commentaires ci-dessous. Je participe aux discussions, mais vous pouvez également me joindre à @reifman sur Twitter. Je suis toujours ouvert aux nouvelles idées de fonctionnalités de Meeting Planner ainsi qu'aux suggestions pour les futurs épisodes de la série..

Pour rappel, tout le code de Meeting Planner est fourni en open source et écrit dans le framework Yii2 pour PHP. Si vous souhaitez en savoir plus sur Yii2, consultez mes séries parallèles Programmer avec Yii2. 

Avant de me plonger dans cette fonctionnalité, je souhaite donner quelques exemples de bugs courants (ou d’oubli) que j’ai rencontrés lors de la création du service.. (Si vous souhaitez simplement en savoir plus sur les URL partageables sécurisées, n'hésitez pas à ignorer cette section.)

Le bug de démarrage Interlude

Alors que de plus en plus de personnes commencent à essayer Meeting Planner, des bugs apparaissent. Et souvent, pendant le développement, je les remarque moi-même. En voici quelques-unes récentes, juste pour vous donner une idée de la vie de startup. 

Il peut être difficile de se concentrer sur le développement commercial et le marketing, le codage de nouvelles fonctionnalités, ainsi que l'identification et la correction des bogues. Le démarrage d'une personne augmente en difficulté à mesure que les fonctionnalités de votre site se développent.

Comme je l'ai écrit plus tôt, j'utilise actuellement Asana pour la planification des fonctionnalités, mais aussi pour le suivi des bogues..

Le bug de l'assignation If

Je suis sûr que si j'avais plus d'expertise en tant que développeur, si je travaillais avec des collègues ou si j'avais plus de temps pour ne pas coder Meeting Planner, je saurais exactement quelle extension d'Atom Editor recherche pour ceux-ci. Si vous le savez, merci de le poster dans les commentaires.

Apparemment, dans une fonctionnalité importante pour vérifier si un spectateur était réellement un participant à une réunion, j'utilisais une tâche pour vérifier. En d'autres termes, je ne demandais pas si le propriétaire était le spectateur. J'en faisais temporairement le cas..

fonction statique publique isAttendee ($ meeting_id, $ user_id) $ m = Meeting :: findOne ($ meeting_id); // sont-ils l'organisateur? // EEEK! if ($ m-> owner_id = $ user_id) return true; 

Vous vous souvenez, deux égaux est une comparaison, un égal est une cession. Tout comme les périodes sont pour les concaténations et les signes plus sont pour les ajouts, sauf en JavaScript, où ils sont difficiles à trouver. (c'est aussi pourquoi Ajax est un enfer en PHP).

Requêtes de base de données qui échouent avec le temps

Au fur et à mesure que mon utilisation de Meeting Planner augmentait, il y avait de plus en plus de réunions dans mes vues à onglets. Et puis j'ai remarqué que des doublons apparaissaient parfois. C'était difficile à détecter plus tôt quand il y avait moins de données.

Mes requêtes de réunion spécifiques à des onglets (planification d'une réunion, réunions confirmées, réunions précédentes, etc.) n'isolaient pas les entrées uniques:

$ planningProvider = new ActiveDataProvider (['query' => Meeting :: find () -> joinWith ('participants') -> where (['owner_id' => Yii :: $ app-> user-> getId ()] ) -> ouWhere (['participant_id' => Yii :: $ app-> user-> getId ()]) -> andWhere (['meeting.status' => [Réunion :: STATUS_PLANNING, Réunion :: STATUS_SENT]] ) / * NÉCESSAIRE D'AJOUTER CE * / -> distinct (), 'sort' => ['defaultOrder' => ['created_at' => SORT_DESC]], 'pagination' => ['pageSize' => 7, ' params '=> array_merge ($ _GET, [' tab '=>' planning ']),],]); 

Ajouter -> distinct () à la requête corrigé.

Yii2 Pagination on Grid Views on Tabbed 

Un autre bug que j'ai rencontré avec plus de données est que les liens de pagination Yii2 me ramènent toujours au premier onglet.

J'ai ajouté un paramètre de requête pour l'onglet en cours qui MeetingController.php actionIndex cherche maintenant:

fonction publique 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 ('participants') -> where (['owner_id' => Yii :: $ app-> user-> getId () ]) -> ouWhere (['participant_id' => Yii :: $ app-> user-> getId ()]) -> andWhere (['meeting.status' => [Réunion :: STATUS_PLANNING, Réunion :: STATUS_SENT] ]) -> distinct (), 'sort' => ['defaultOrder' => ['created_at' => SORT_DESC]], 'pagination' => ['pageSize' => 7, 'params' => array_merge ($ _GET, ['tab' => 'planning']),],]);

En outre, j'ai chargé le paramètres de pagination pour fusionner le paramètre d’onglet actuel. Lorsque les utilisateurs cliquent sur un lien de page différent, l'onglet en cours est maintenant inclus..

Enfin, j’ai aussi informé le / frontend/views/meeting/index.php, en définissant l’onglet actif à partir du paramètre de requête:

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

Ce ne sont là que quelques bons exemples du type de bugs quotidiens que je rencontre lors de la création d’une start-up dans le cadre de Meeting Planner..

Passons maintenant à la création d'invitations via des URL de raccourci comme promis..

Création d'URL de raccourcis partageables sécurisés

Réflexion sur la sécurité pour les URL

Pour rendre plus difficile l'explosion aléatoire d'URL de conjectures dans l'invitation à une réunion, il me fallait une clé unique, associée à un code impossible à deviner..

J'ai décidé d'utiliser le nom d'utilisateur de la personne comme clé. Chaque utilisateur aurait un grand nombre de codes de réunion presque impossibles à deviner.

Ainsi, par exemple, une URL de réunion peut être https://meetingplanner.io/presidenthillary/X1Y2Z3A7C9.

Pour le code, j'ai décidé d'utiliser huit caractères alphanumériques sensibles à la casse. En d'autres termes, chaque caractère serait a-z, A-Z ou 0-9, soit 62 possibilités pour chaque caractère..

Le nombre total de possibilités pour chaque utilisateur est de 218 340 105 584 896, soit plus de 218 billions de dollars. Oh, et vous devez connaître le nom d'utilisateur de votre cible pour commencer! Il serait beaucoup plus facile de pirater le compte de messagerie d'un participant.

Ajout du code de sécurité pour chaque réunion

Pour ajouter un code de sécurité à toutes les réunions existantes, j'ai créé une migration, m160902_174350_extend_meeting_for_identifier.php:

La classe m160902_174350_extend_meeting_for_identifier étend la migration fonction publique up () $ tableOptions = null; if ($ this-> db-> driverName === 'mysql') $ tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB';  $ this-> addColumn ('% meeting', 'identificateur', Schema :: TYPE_STRING. 'NOT NULL'); $ all = Meeting :: find () -> où (['identifier' => "]) -> tous (); foreach ($ tous comme $ m) $ m-> identifiant = Yii :: $ app-> security-> generateRandomString (8); $ m-> update ();

Vous remarquerez que dans cette migration, j’utilise du code pour créer des chaînes aléatoires pour chaque réunion existante, c.-à-d.. Yii :: $ app-> security-> generateRandomString (8);.

Ce n'est pas souvent que j'écris du code dans une migration pour mettre à jour des zones existantes de la base de données. Dans ce cas, cela fonctionne bien. D'autres fois, j'ai utilisé le modèle /frontend/models/Fix.php.

Aussi dans Réunion :: beforeSave (), J'ai ajouté du code automatisé pour générer un identifiant pour toutes les réunions futures:

fonction publique beforeSave ($ insert) if (parent :: beforeSave ($ insert)) if ($ insert) $ this-> identificateur = Yii :: $ app-> security-> generateRandomString (8);  return true;  

Extension du routage Yii

Bien qu’il aurait été plus facile d’inclure un préfixe de contrôleur tel que / m / nom d'utilisateur / code d'identité, Je voulais que les liens soient simples, sans préfixe supplémentaire. Cela nécessitait l'extension du routage Yii. 

Si j'avais gardé cela dans son propre modèle pour le préfixe et le nom d'utilisateur, j'aurais peut-être pu utiliser ce que j'ai écrit dans Yii2 Sluggable Behaviors et construire votre démarrage: Géolocalisation et Google Places.

Au lieu de cela, j'ai ajouté  '/'=>' réunion / identité ', qui mappe n'importe quel nom d'utilisateur avec une chaîne d'identité au MeetingController actionIdentity () méthode.

'urlManager' => ['class' => 'yii \ web \ UrlManager', 'enablePrettyUrl' => true, 'showScriptName' => false, // 'enableStrictParsing' => false, 'rules' => ['place '=>' place ',' place / yours '=>' place / yours ',' place / create '=>' place / create ',' place / create_geo '=>' place / create_geo ',' place / create_place_google '=>' place / create_place_google ',' place / view /'=>' place / view ',' place / update /'=>' place / update ',' place /'=>' place / slug ','/'=>'/vue', '//'=>'/',' daemon /'=>' démon /', // incl huit caractère action' site /'=>' site /', // incl huit action de caractère' features '=>' site / features ',' about '=>' site / about ','/'=>' meeting / identity ', // note - les actions actuellement avec 8 lettres et aucun paramètre échoueront'/'=>'/',],],

Avec cela, j'ai rencontré quelques problèmes. Je devais réorganiser les règles et définir des routes statiques pour toutes les actions sur huit caractères pouvant ressembler à un nom d'utilisateur (au lieu d'un contrôleur) et à une méthode (au lieu d'une clé d'identité)..

Par exemple, https://meetingplanner.io/site/features est mappé sur un utilisateur nommé site disposant d'un ID de réunion sécurisé de "fonctionnalités" au lieu de la nouvelle table des fonctionnalités de Meeting Planner.

Mais une fois que je me suis orienté vers les problèmes, tout a bien fonctionné.

La méthode d'identité du contrôleur de réunion

Ensuite, j'ai construit actionIdentity () dans MeetingController:

 fonction publique actionIdentity () // liste de chemin de récupération ($ nom_utilisateur, $ identificateur) = explode ("/", Yii :: $ app-> request-> getPathInfo ()); // vérifie l'identifiant de la réunion $ m = Meeting :: find () -> où (['identifiant' => $ identifiant]) -> un (); if (is_null ($ m) || ($ m-> propriétaire-> nom_utilisateur! = $ nom_utilisateur)) // échec d'accès, retournez $ this-> redirect (['site / authfailure']);  // identifiant est authentique si (Yii :: $ app-> utilisateur-> isGuest) // rediriger vers le formulaire de participation du participant return $ this-> redirect (['/ participant / join', 'id_reunion' => $ m -> id, 'identifiant' => $ identifiant]);  else $ user_id = Yii :: $ app-> user-> getId (); if (! Meeting :: isAttendee ($ m-> id, $ id_utilisateur)) // s'il ne s'agit pas d'un participant - ajoutez-le en tant que participant Participant :: add ($ m-> id, $ id_utilisateur, $ m-> owner_id);  return $ this-> actionView ($ m-> id); 

Tout d'abord, il vérifie que le nom d'utilisateur et l'identité correspondent à un utilisateur existant et à une réunion existante. Si non, nous les envoyons à échec automatique.

Si l'utilisateur est déjà connecté, nous l'ajoutons automatiquement en tant que participant à cette page et nous le redirigeons vers la page d'affichage de la réunion..

S'ils ne le sont pas, nous les envoyons à un contrôleur de participant Action de jointure pour vous connecter ou vous connecter..

Participant demandant à participer à une réunion

Par exemple, disons que je reçois l'invitation suivante d'un ami par courrier électronique:

https://meetingplanner.io/tomeMcFarline/JzRq1a42. On me montrera cette page:

Si l'utilisateur souhaite s'inscrire à Meeting Planner via un réseau social, nous pourrons valider son adresse e-mail..

J'ai donc défini l'URL de retour Yii (une page vers laquelle l'utilisateur est redirigé après une inscription ou une connexion réussie) qui les renverra à la page d'affichage de la réunion après l'authentification..

// set return url Yii :: $ app-> user-> setReturnUrl ($ m-> getSharingUrl ());

Pour la plupart, l'authentification sociale, la connexion et / ou l'inscription sont gérées par le code que j'ai décrit dans Construction de votre démarrage: simplifier Onramp avec OAuth..

Si le participant est nouveau, il fournira son nom, son prénom et son adresse électronique. Nous les ajouterons à la réunion en tant que participant non vérifié, comme ce que nous faisons lorsqu'un utilisateur invite une personne en ajoutant une nouvelle adresse électronique à la réunion..

$ model = new Participant; $ model-> meeting_id = $ meeting_id;… $ model-> guest_by = $ m-> owner_id; $ model-> status = Participant :: STATUS_DEFAULT; if (! $ validationError && $ model-> validate ()) $ model-> participant_id = Utilisateur :: addUserFromEmail ($ model-> email); $ model-> save (); // cherche les emails pour voir s'ils existent Meeting :: displayNotificationHint ($ meeting_id); $ user = User :: findOne ($ model-> participant_id); Yii :: $ app-> user-> login ($ user); return $ this-> redirect (['/ meeting / view', 'id' => $ meeting_id]); 

Vous vous demandez maintenant, hey Jeff, c'est quoi le problème? aujourd'hui? C'est simple, non? Nous venons d'ajouter un nouvel utilisateur à une réunion.

En codant cela, j'ai réalisé que je créais un énorme trou dans la vie privée.

Exemple amusant de trou de sécurité

Disons que Tom McFarlin n'a rien à faire un jour et décide de jouer avec moi (et Dieu). Il créera une nouvelle réunion et, sachant que le dalaï-lama est un utilisateur régulier de Meeting Planner (en raison de toutes ses réunions spirituelles), McFarlin l'ajoutera à sa réunion en utilisant le courrier électronique [email protected]..

Ensuite, il récupérera son URL de raccourci sécurisée. Me suivre?

Ensuite, McFarlin ouvrira un navigateur différent, son URL de raccourci sécurisé et prétendra qu'il est le dalaï-lama qui vient de recevoir une autre invitation par courrier électronique de Tom, c'est-à-dire qu'il tentera de rejoindre sa propre réunion comme s'il était le dalaï-lama. c'est-à-dire Dalai, Lama, [email protected].

Au départ, je présumais qu'il était peu probable qu'une personne devine jamais l'URL sécurisée. Si cela se produisait, je laisserais simplement une personne se connecter de cette manière.. 

Mais cela donnerait au dangereux McFarlin un accès au compte du Dalaï Lama (en partie parce que je n’ai pas encore créé le mode d’accès limité pour les utilisateurs qui entrent par l’URL pour pouvoir uniquement voir une réunion jusqu’à ce qu’ils se connectent).

Oui, mon code initial a fonctionné de cette façon. Et puis j'ai reçu un appel du ciel et je l'ai signalé. 

Et si McFarlin avait invité Sally et que Sally avait transmis l’URL sécurisée à Bill Gates? En ajoutant manuellement Bill Gates à la réunion en premier, McFarlin pourrait accéder à toutes les réunions de Gates avec cette astuce..

Le nouveau code nécessite un participant utilisant l'URL sécurisée qui a déjà été ajouté par hasard à la réunion pour se connecter manuellement. Ici se trouve le code:

if ($ model-> load (Yii :: $ app-> request-> post ())) // demander si la personne qui se joint existe déjà dans le tableau des utilisateurs // a peut-être été ajouté à une invitation par l'organisateur ou déjà un utilisateur enregistré $ person = User :: find () -> where (['email' => $ model-> email]) -> one (); if (! is_null ($ person)) // le courrier électronique d'un utilisateur existe déjà // améliore son profil $ postedVars = Yii :: $ app-> request-> post (); if (! empty ($ postedVars ['Participant'] ['prénom']))) $ modèle-> prénom = $ postedVars ['Participant'] ['prenom'];  if (! empty ($ postedVars ['Participant'] ['nom de famille']))) $ modèle-> nom de famille = $ postedVars ['Participant'] ['nom de famille'];  UserProfile :: améliorer ($ person-> id, $ modèle-> prénom, $ modèle-> nom); // sont-ils des participants si (Meeting :: isAttendee ($ model-> meeting_id, $ person-> id)) / * // à faire - ceci doit être changé en mode d'accès restreint ou supprimé $ identity = $ person-> findIdentity ($ person-> id); Yii :: $ app-> user-> login ($ identity); // à faire - met à jour le profil utilisateur avec le prénom et le nom $ this-> redirect (['meeting / view', 'id' => $ model-> meeting_id]);  else * / // attention - ne désactivez pas cette exigence // une personne peut ajouter une célébrité à une réunion en utilisant son email avec n'importe quel code de réunion et en se connectant à son compte Yii :: $ app-> getSession () -> setFlash ('warning', Yii :: t ('frontend', 'Puisque vous avez déjà un compte, connectez-vous ci-dessous.')); return $ this-> redirect (['/ site / login']); 

Je vais corriger cela une fois que je créerai le mode d'accès restreint à une seule réunion.

Je n'y aurais peut-être pas pensé si je n'avais pas su à quel point McFarlin était sournois. Phew. Encore une reine sauvée de l'empoisonnement.

Prendre probablement mon long week-end dans la nature m'a aussi permis de mieux comprendre le ciel.

Qu'y a-t-il dans le pipeline?

Domaine public via Google et Hawaii Image du jour

J'espère que vous avez apprécié cet épisode sur la création d'URL sécurisées pour inviter des personnes à des réunions. J'imagine qu'il est hautement réutilisable pour d'autres scénarios dans vos propres services. 

Vous pouvez aussi probablement dire que j'apprécie le potentiel que Meeting Planner semble avoir à ce stade ou que je travaille depuis trop longtemps..

En fin de compte, la création d'invitations URL sécurisées offre également la possibilité de proposer aux utilisateurs une page de planification publique. Par exemple, je peux partager mon URL publique avec Meeting Planner avec des amis et, par exemple, me programmer à https://meetingplanner.io/username.

À l'avenir, je pourrais même étendre Meeting Planner pour offrir des fonctionnalités d'abonnement permettant aux professionnels de prendre des rendez-vous sur leur page publique Meeting Planner. Cependant, j'ai d'autres idées plus excitantes. Ce territoire est bien implanté par d'autres entreprises à vocation commerciale.

Surfer sur la vague

Si vous ne l'avez pas encore fait, rendez-vous maintenant à votre première réunion avec Meeting Planner! Essayez de partager l'URL de raccourci de votre réunion et gardez le secret sur notre dieu éditorial, Tom McFarlin..

Vous pouvez également me contacter @reifman. Je suis toujours ouvert aux nouvelles idées de fonctionnalités et suggestions de sujets pour les prochains tutoriels. Ou essayez notre service d'assistance et ouvrez un rapport de bogue ou un ticket de demande de fonctionnalité. 

Un tutoriel sur le financement participatif est également en préparation. Veuillez suivre notre page WeFunder Meeting Planner..

Restez à l'affût de tout cela et de plusieurs autres tutoriels à venir en consultant la série Construire son démarrage avec PHP. 

Liens connexes