Статьи

Создание вашего стартапа: приглашайте людей через URL

Конечный продукт
Что вы будете создавать

Это руководство является частью серии « Создай свой стартап с помощью PHP» на Envato Tuts +. В этой серии я проведу вас через запуск стартапа от концепции до реальности, используя мое приложение Meeting Planner в качестве примера из реальной жизни. На каждом этапе я буду публиковать код Планировщика собраний в качестве примеров с открытым исходным кодом, из которых вы можете извлечь уроки. Я также буду решать вопросы, связанные с бизнесом по мере их возникновения.

Добро пожаловать! Я недавно вернулся из своего любимого места в мире, о котором я упоминал в конце последнего эпизода . После завершения нескольких встреч участников , я взял перерыв в природе.

Создание вашего стартапа Безопасный общий URL-адрес приглашения - мое любимое место в лесу

Сегодня я добавлю возможность приглашать участников собрания, предоставляя безопасный URL-адрес, связанный с вашей встречей. Это будет особенно полезно для планирования групповых встреч. Например, если вы хотите пригласить 30 человек, иногда проще отправить электронное письмо всем с URL-адресом приглашения.

Если вы еще этого не сделали, попробуйте назначить встречу своей группы сегодня ! Пригласите нескольких друзей встретиться с вами для чайного гриба, кавы или кофе. Поделитесь своими мыслями и отзывами об опыте каждого в комментариях ниже. Я участвую в обсуждениях, но вы также можете связаться со мной @reifman в Twitter. Я всегда открыт для новых идей для планировщика собраний, а также предложений для будущих серий.

Напоминаем, что весь код для Meeting Planner предоставляется с открытым исходным кодом и написан на Yii2 Framework для PHP. Если вы хотите узнать больше о Yii2, ознакомьтесь с моей параллельной серией Программирование с Yii2 .

Прежде чем углубиться в эту функцию, я хочу привести несколько примеров некоторых типичных дневных ошибок (или упущений), с которыми я столкнулся при создании сервиса. (Если вы просто хотите прочитать о безопасных общедоступных URL-адресах, пожалуйста, пропустите этот раздел.)

Поскольку все больше и больше людей начинают пробовать Meeting Planner, появляются ошибки. И часто во время разработки я замечаю их сам. Вот несколько последних, просто чтобы дать вам представление о жизни стартапа.

Может быть трудно сосредоточиться на развитии бизнеса и маркетинге, кодировании новых функций, выявлении и исправлении ошибок. Запуск одного человека возрастает с ростом возможностей вашего сайта.

Как я уже писал ранее, в настоящее время я использую Asana для планирования функций, а также для отслеживания ошибок.

Я уверен, что если бы я имел больше опыта как разработчик, работал с коллегами или имел больше времени, чтобы не программировать Meeting Planner, я бы точно знал, какое расширение Atom Editor ищет для них. Если вы знаете, пожалуйста, напишите об этом в комментариях.

По-видимому, в важной функции проверки того, действительно ли зритель был участником собрания, я использовал назначение для проверки. Другими словами, я не спрашивал, был ли владелец зрителем — я временно делал это.

1
2
3
4
5
6
7
public static function isAttendee($meeting_id,$user_id) {
 $m = Meeting::findOne($meeting_id);
 // are they the organizer?
 // EEEK!
 if ($m->owner_id = $user_id) {
   return true;
 }

Вы помните, два равных — это сравнение, одно равное — это задание. Точно так же, как точки — для конкатенаций, а знаки плюс — для сложения, за исключением JavaScript, где очень трудно найти ошибки (именно поэтому Ajax ад в PHP).

По мере того, как увеличивалось мое использование Meeting Planner, в моих представлениях с вкладками появлялось все больше и больше встреч. А потом я заметил, что иногда появляются дубликаты. Это было трудно обнаружить раньше, когда было меньше данных.

Мои специфичные для вкладок запросы на собрания (т. Е. Планирование собрания, подтвержденные собрания, прошедшие собрания и т. Д.) Не изолировали уникальные записи:

01
02
03
04
05
06
07
08
09
10
11
12
13
$planningProvider = new ActiveDataProvider([
     ‘query’ => Meeting::find()->joinWith(‘participants’)
     ->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’]),
       ],
 ]);

Добавление ->distinct() к запросу исправило это.

Еще одна ошибка, с которой я столкнулся с большим количеством данных, заключалась в том, что ссылки на страницы Yii2 всегда возвращали меня на первую вкладку.

Создание вашего безопасного защищенного общего URL-адреса приглашения - разбиение на страницы собраний

Я добавил параметр запроса для текущей вкладки, которую теперь ищет MeetingController.php actionIndex :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
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(‘participants’)->where([‘owner_id’=>Yii::$app->user->getId()])->orWhere([‘participant_id’=>Yii::$app->user->getId()])->andWhere([‘meeting.status’=>[Meeting::STATUS_PLANNING,Meeting::STATUS_SENT]])->distinct(),
           ‘sort’=> [‘defaultOrder’ => [‘created_at’=>SORT_DESC]],
           ‘pagination’ => [
               ‘pageSize’ => 7,
               ‘params’ => array_merge($_GET, [‘tab’ => ‘planning’]),
             ],
       ]);

Также я дал указание параметрам pagination params объединить текущую настройку вкладки. Когда пользователи нажимают на другую ссылку на страницу, текущая вкладка теперь включена.

Наконец, я также сообщил об этом /frontend/views/meeting/index.php, установив активную вкладку из параметра запроса:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!— Tab panes —>
<div class=»tab-content»>
  <div class=»tab-pane <?= ($tab==’planning’?’active’:») ?>» id=»planning»>
    <div class=»meeting-index»>
      <?php
      ?>
      <?= $this->render(‘_grid’, [
          ‘mode’=>’planning’,
          ‘dataProvider’ => $planningProvider,
          ‘timezone’=>$timezone,
      ]) ?>
 
    </div> <!— end of planning meetings tab —>
  </div>
  <div class=»tab-pane <?= ($tab==’upcoming’?’active’:») ?>» id=»upcoming»>
    <div class=»meeting-index»>
      <?= $this->render(‘_grid’, [
          ‘mode’=>’upcoming’,
          ‘dataProvider’ => $upcomingProvider,
          ‘timezone’=>$timezone,
      ]) ?>
 
      </div> <!— end of upcoming meetings tab —>
  </div>
  <div class=»tab-pane <?= ($tab==’past’?’active’:») ?>» id=»past»>
 
    <?= $this->render(‘_grid’, [
        ‘mode’=>’past’,
        ‘dataProvider’ => $pastProvider,
        ‘timezone’=>$timezone,
    ]) ?>
  </div> <!— end of past meetings tab —>

Это всего лишь несколько хороших примеров повседневных ошибок, с которыми я сталкиваюсь при создании стартапа в рамках Meeting Planner.

Теперь давайте погрузимся в создание приглашений с помощью ярлыков URL, как и обещали.

Чтобы случайному потоку предположений URL-адресов было сложнее проникнуть в чье-либо приглашение на собрание, мне нужно было иметь достаточно уникальный ключ в сочетании с неуязвимым кодом.

Я решил использовать имя пользователя человека в качестве ключа. У каждого пользователя будет большое количество почти неосуществимых кодов собраний.

Так, например, URL собрания может быть https://meetingplanner.io/presidenthillary/X1Y2Z3A7C9 .

Для кода я решил использовать восемь буквенно-цифровых символов с учетом регистра. Другими словами, каждый символ будет az, AZ или 0-9, по существу 62 варианта для каждого символа.

Общее количество возможностей для каждого пользователя составляет 218 340 105 584 896 — более 218 трлн. О, и вы должны знать имя пользователя вашей цели, чтобы начать! Было бы намного проще взломать учетную запись участника электронной почты.

Чтобы добавить код безопасности для всех существующих собраний, я создал миграцию m160902_174350_extend_meeting_for_identifier.php:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
class m160902_174350_extend_meeting_for_identifier extends Migration
{
  public function up()
  {
    $tableOptions = null;
    if ($this->db->driverName === ‘mysql’) {
        $tableOptions = ‘CHARACTER 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 as $m) {
      $m->identifier = Yii::$app->security->generateRandomString(8);
      $m->update();
    }
  }

Вы заметите, что в этой миграции я фактически использую код для создания случайных строк для каждого существующего собрания, то есть Yii::$app->security->generateRandomString(8); ,

Я не часто пишу код в миграции для обновления существующих областей базы данных. В этом случае все работает плавно. В других случаях я использовал модель /frontend/models/Fix.php.

Также в Meeting::beforeSave() я добавил автоматический код для генерации идентификатора для всех будущих собраний:

1
2
3
4
5
6
7
8
9
public function beforeSave($insert)
     {
         if (parent::beforeSave($insert)) {
           if ($insert) {
             $this->identifier = Yii::$app->security->generateRandomString(8);
           }
         }
         return true;
     }

Хотя было бы проще всего включить префикс контроллера, такой как /m/username/identity-code , я хотел, чтобы ссылки были простыми, без дополнительного префикса. Это потребовало расширения Yii Routing .

Если бы я сохранил это в своей собственной модели для префикса и имени пользователя, я мог бы использовать то, о чем писал в « Сладких поведениях Yii2 и Построении стартапа: геолокация и Google Places» .

Вместо этого я добавил '<username>/<identity:[A-Za-z0-9_-]{8}>' => 'meeting/identity', которое сопоставляет любое имя пользователя со строкой идентификатора методу actionIdentity() ,

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
‘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/<id:\d+>’ => ‘place/view’,
             ‘place/update/<id:\d+>’ => ‘place/update’,
             ‘place/<slug>’ => ‘place/slug’,
             ‘<controller:\w+>/<id:\d+>’ => ‘<controller>/view’,
             ‘<controller:\w+>/<action:\w+>/<id:\d+>’ => ‘<controller>/<action>’,
             ‘daemon/<action>’ => ‘daemon/<action>’, // incl eight char action
             ‘site/<action>’ => ‘site/<action>’, // incl eight char action
             ‘features’ => ‘site/features’,
             ‘about’ => ‘site/about’,
             ‘<username>/<identity:[A-Za-z0-9_-]{8}>’ => ‘meeting/identity’,
             // note — currently actions with 8 letters and no params will fail
             ‘<controller:\w+>/<action:\w+>’ => ‘<controller>/<action>’,
           ],
       ],

С этим я столкнулся с несколькими проблемами. Мне пришлось изменить порядок правил и поместить в статические маршруты любые восемь символьных действий, которые могли бы выглядеть как имя пользователя (вместо контроллера) и метод (вместо ключа идентификации).

Например, https://meetingplanner.io/site/features сопоставлен с сайтом с именем пользователя, имеющим защищенный идентификатор собрания «функций» вместо новой классной таблицы функций Meeting Planner.

Но как только я сориентировался на проблемы, все заработало нормально.

Затем я создал actionIdentity() в MeetingController:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public function actionIdentity()
   {
     // fetch path
     list($username,$identifier) = explode(«/»,Yii::$app->request->getPathInfo());
     // verify the meeting identifier
     $m = Meeting::find()
       ->where([‘identifier’=>$identifier])
       ->one();
     if (is_null($m) || ($m->owner->username != $username)) {
       // access failure
       return $this->redirect([‘site/authfailure’]);
     }
     // identifier is authentic
     if (Yii::$app->user->isGuest) {
       // redir to Participant join form
       return $this->redirect([‘/participant/join’,’meeting_id’=>$m->id,’identifier’=>$identifier]);
     } else {
       $user_id = Yii::$app->user->getId();
       if (!Meeting::isAttendee($m->id,$user_id)) {
           // if not an attendee — add them as a participant
           Participant::add($m->id,$user_id,$m->owner_id);
       }
       return $this->actionView($m->id);
     }

Во-первых, он проверяет, что имя пользователя и личность соответствуют существующему пользователю и существующему собранию. Если нет, мы отправляем их в authfailure .

Если пользователь уже вошел в систему, мы автоматически добавим его в качестве участника на эту страницу и перенаправим на страницу просмотра собрания.

Если это не так, мы отправляем их контроллеру Участника, присоединяющемуся к действию для адресации входа или регистрации.

Например, допустим, я получил следующее электронное приглашение от друга:

https://meetingplanner.io/tomeMcFarline/JzRq1a42 . Мне будет показана эта страница:

Создание вашего безопасного защищенного разделяемого URL-адреса приглашения - страница присоединения участника к URL-адресу приглашения

Если пользователь желает зарегистрироваться в Meeting Planner через социальную сеть, мы сможем подтвердить его адрес электронной почты.

Поэтому я установил URL возврата Yii (страницу, на которую перенаправляется пользователь после успешной регистрации или входа в систему), которая будет возвращать их на страницу просмотра собрания после проверки подлинности.

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

В большинстве случаев социальная аутентификация, вход в систему и / или регистрация осуществляются с помощью кода, который я описал в разделе «Создание стартапа: упрощение Onramp с помощью OAuth» .

Если участник новый, он сообщит свое имя, фамилию и адрес электронной почты. Мы добавим их на собрание в качестве непроверенного участника, аналогично тому, что мы делаем, когда пользователь приглашает кого-то, добавляя новый адрес электронной почты для собрания.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
$model = new Participant;
$model->meeting_id = $meeting_id;
$model->invited_by = $m->owner_id;
$model->status = Participant::STATUS_DEFAULT;
if (!$validationError && $model->validate()) {
  $model->participant_id = User::addUserFromEmail($model->email);
  $model->save();
  // look up email to see if they exist
  Meeting::displayNotificationHint($meeting_id);
  $user = User::findOne($model->participant_id);
  Yii::$app->user->login($user);
  return $this->redirect([‘/meeting/view’, ‘id’ => $meeting_id]);
}

Вы задаетесь вопросом прямо сейчас, эй Джефф, что с ... сегодня? Это просто, правда? Мы просто добавляем нового пользователя на встречу.

Кодируя это, я понял, что создаю огромную дыру в конфиденциальности.

Допустим, однажды Тому Макфарлину нечего делать, и он решает связываться со мной (и Богом). Он создаст новую встречу, и, зная, что Далай-лама является постоянным пользователем Планировщика собраний (из-за всех его духовных встреч), Макфарлин добавит его на свою встречу, используя электронную почту [email protected].

Затем он возьмет свой модный безопасный ярлык URL. За мной?

Затем Макфарлин откроет другой браузер и откроет свой безопасный URL-адрес ярлыка и сделает вид, что он Далай-лама, который только что получил другое приглашение от Тома по электронной почте, то есть он попытается присоединиться к собственной встрече, как будто он Далай-лама. то есть Далай, Лама, [email protected].

Первоначально я предполагал, что не было никакой вероятности, что кто-то когда-нибудь угадает безопасный URL, поэтому, если это произойдет, я просто позволю человеку войти таким образом.

Но это дало бы опасный доступ Макфарлина к учетной записи Далай-ламы (отчасти потому, что я еще не создал режим ограниченного доступа для пользователей, заходящих по URL-адресам, чтобы они могли видеть только одно собрание, пока они не войдут в систему).

Да, мой исходный код работал таким образом. А потом мне позвонили с небес и указали на это.

Что если Макфарлин пригласил Салли и Салли переправили безопасный URL Биллу Гейтсу? Добавив Билла Гейтса вручную на собрание первым, Макфарлин смог получить доступ ко всем встречам Гейтса с помощью этого трюка.

Новый код требует, чтобы участник, использующий безопасный URL-адрес, который уже был случайно добавлен в собрание, мог войти в систему вручную. Вот код ...

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
if ($model->load(Yii::$app->request->post())) {
 // asking does the person joining already exist in User table
 // might have been added to invitation by organizer or might already be a registered user
 $person = User::find()->where([’email’=>$model->email])->one();
 if (!is_null($person)) {
     // user email already exists
     // improve their profile
     $postedVars = Yii::$app->request->post();
     if (!empty($postedVars[‘Participant’][‘firstname’])) {
         $model->firstname = $postedVars[‘Participant’][‘firstname’];
     }
     if (!empty($postedVars[‘Participant’][‘lastname’])) {
         $model->lastname = $postedVars[‘Participant’][‘lastname’];
     }
     UserProfile::improve($person->id,$model->firstname,$model->lastname);
     // are they an attendee
   if (Meeting::isAttendee($model->meeting_id,$person->id)) {
     /*
       // to do note — this has to be changed to restricted access mode or removed
       $identity = $person->findIdentity($person->id);
       Yii::$app->user->login($identity);
       // to do — update user profile with first and last name
       $this->redirect([‘meeting/view’,’id’=>$model->meeting_id]);
     } else {
     */
       // caution — don’t turn off this requirement
       // a person could add a celebrity to a meeting by using their email with any meeting code and login with their account
       Yii::$app->getSession()->setFlash(‘warning’, Yii::t(‘frontend’,’Since you have an account already, please login below.’));
       return $this->redirect([‘/site/login’]);
     }
   }

Я исправлю это снова, как только создам режим ограниченного доступа для одной встречи.

Я бы не подумал об этом, если бы не знал, насколько коварен Макфарлин . Уф. Еще одна королева спасена от отравления.

Создание вашего стартапа Безопасный общий URL-адрес приглашения - Коварный редакционный бог Том Макфарлин или нет

Вероятно, мои долгие выходные на природе помогли мне больше познакомиться с небесами.

Создание вашего безопасного защищенного общего URL-адреса приглашения - изображение серфингового конвейера
Общественное достояние через Google & Hawaii Picture of the Day

Надеюсь, вам понравился этот эпизод по созданию безопасных URL-адресов для приглашения людей на встречи. Я полагаю, что он может быть многократно использован для других сценариев в ваших собственных сервисах.

Вы также можете, вероятно, сказать, что я либо наслаждаюсь потенциальным Планировщиком собраний, либо, возможно, слишком долго работал.

В конечном счете, создание безопасных URL-приглашений также открывает возможность предложить пользователям страницу общего планирования. Например, я могу поделиться своим общедоступным URL-адресом Планировщика собраний с друзьями и, скажем, просто запланировать меня на https://meetingplanner.io/username .

В будущем я мог бы даже расширить Планировщик собраний, чтобы предлагать подписчикам функции подписки на встречи на их общедоступной странице Планировщика собраний. Однако у меня есть другие более интересные идеи. Эта территория хорошо защищена другими бизнес-ориентированными компаниями.

Если вы еще этого не сделали, запланируйте свою первую встречу с Meeting Planner прямо сейчас! Попробуйте поделиться кратким URL-адресом вашей встречи и держите его в секрете от нашего редактора Тома Макфарлина.

Вы также можете обратиться ко мне @reifman . Я всегда открыт для новых идей и тематических предложений для будущих уроков. Или попробуйте нашу службу поддержки и откройте отчет об ошибке или запрос на добавление функции.

Учебное пособие по краудфандингу также находится в разработке, поэтому, пожалуйста, следуйте нашей странице WeFunder Meeting Planner .

Следите за всеми этими и другими учебниками, ознакомившись с серией « Построение стартапа на PHP» .