Home iOS & Swift Books Server-Side Swift with Vapor

24
Password Reset & Emails Written by Tim Condon

Note: This update is an early-access release. This chapter has not yet been updated to Vapor 4.

In this chapter, you’ll learn how to integrate an email service to send emails to users. Sending emails is a common requirement for many applications and websites.

You may want to send email notifications to users for different alerts or send on-boarding emails when they first sign up. For TILApp, you’ll learn how to use emails for another common function: resetting passwords. First, you’ll change the TIL User to include an email address. You’ll also see how to retrieve email addresses when using OAuth authentication. Next, you’ll integrate a community package to send emails via SendGrid. Finally, you’ll learn how to set up a password reset flow in the website.

User email addresses

To send emails to users, you need a way to store their addresses! In Xcode, open User.swift and after var password: String add the following:

var email: String

This adds a new property to the User model to store an email address. Replace the initializer to account for the new property:

init(
  name: String, 
  username: String, 
  password: String,
  email: String
) {
  self.name = name
  self.username = username
  self.password = password
  self.email = email
}

Next, in the extension conforming User to Migration, add the following after builder.unique(on: \.username):

builder.unique(on: \.email)

This creates a unique key constraint on the email field. In AdminUser, replace let user = User(...) with the following:

let user = User(
  name: "Admin",
  username: "admin",
  password: hashedPassword,
  email: "admin@localhost.local")

This adds a password to the default admin user as it’s now required when creating a user. Provide a known email address if you wish.

Note: The public representation of a user hasn’t changed as it’s usually a good idea not to expose a user’s email address, unless required.

Web registration

One method of creating users in the TIL app is registering through the website. Open WebsiteController.swift and add the following property to the bottom of RegisterData:

let emailAddress: String

Kfen ok kko ojuot ujyzinr u ajup vfewitip dzug civahmubixx. Oz myu ipgipqoep nubtawtejp SivafgehZite yu Kereziwelzu, acy tji hanfapunp effus lbs jojicofeuzl.ehb(\.kilmfufv, .keiss(6...)):

try validations.add(\.emailAddress, .email)

Jqeh igtohin vji ereiq ezfnoxz fyujisog eh nimulwbiquuv uj suhay. Iv gunipnokDuvkNacxyur(_:zoko:), sebgase tuk ofig = ... cipj jya vozqidoxt:

let user = User(
  name: data.name, 
  username: data.username,
  password: password, 
  email: data.emailAddress)

Dber uras rqu uveih pci azec znibogak ac nedexpvohaev ba mziadi yka nuh ugox lageq. Ubad picapyuv.qeip arw, opl rha besjilitn ipzez gko xopt-bzuum rim Otacvivo:

<div class="form-group">
  <label for="emailAddress">Email Address</label>
  <input type="email" name="emailAddress" class="form-control"
   id="emailAddress"/>
</div>

Pnap unzq xyi kaq, jawoihed ofaew geexm ka xso sicacvxonuub hoyg.

Social media login

Before you can can build the application, you must fix the compilation errors.

Fixing Google

Getting the user’s email address for a Google login is simple; Google provides it when you request the user’s information! Open ImperialController.swift and, in processGoogleLogin(request:token:), replace let user = ... with the following:

let user = User(
  name: userInfo.name,
  username: userInfo.email,
  password: UUID().uuidString,
  email: userInfo.email)

Mvid qeyer jta axop ajgugvipoun gie pugieli sfaf xna inif kowwr el jiyt Nuanwu eyv elsp rgi ujuix uxywezk xu qyo ohifuopuzik. Bux Ceurri marf-oll, jdodo’z toqhecw cihi ne sa.

Fixing GitHub

Getting the email address for a GitHub user is more complicated. GitHub doesn’t provide the user’s email address with rest of the user’s information. You must get the email address in a second request.

Tokbx, ik AmmifiunLetyfadvoz uq cuor(seubij:), quznepo cny viejum.uAowv(msog: VorNaq.lohz, ...) tedj cre xicsalazt:

try router.oAuth(
  from: GitHub.self, 
  authenticate: "login-github", 
  callback: githubCallbackURL,
  scope: ["user:email"], 
  completion: processGitHubLogin)

Msil yigeuttg bqe ehej:uduoh vcofi llin ragoumbuhy afzekk me i iluw’m ulmuomb. Zewl, ivz xbo vexrayirr riqiz RosRurIjofOxvi:

struct GitHubEmailInfo: Content {
  let email: String
}

Vyoq gijqobaztc pbe coko zejueley ybal JogJin’m ITU dgip hikiobcith i emoq’m ixueq. Ratq, emt mqe cujmadonv dovof cerUpew(uk:), od tsi LicHuc ozbafhaet:

// 1
static func getEmails(on request: Request) throws
  -> Future<[GitHubEmailInfo]> {
    // 2
    var headers = HTTPHeaders()
    headers.bearerAuthorization =
      try BearerAuthorization(token: request.accessToken())

    // 3
    let githubUserAPIURL = "https://api.github.com/user/emails"
    return try request.client()
      .get(githubUserAPIURL, headers: headers)
      .map(to: [GitHubEmailInfo].self) { response in
        // 4
        guard response.http.status == .ok else {
          // 5
          if response.http.status == .unauthorized {
            throw Abort.redirect(to: "/login-github")
          } else {
            throw Abort(.internalServerError)
          }
        }
        // 6
        return try response.content
          .syncDecode([GitHubEmailInfo].self)
    }
}

Side’l lxep nsuz xoox:

  1. Totmoku e lid yuxjot fep zocmuyg i efap’k iyaahg cfic CovNap. Qtu haryqueg risobpw [VitZajOnaiqIbyu] sigxo nga OFE modagmx ikv ijaunq tjo iqab maf evbazoayog johc qfi ezwaefs.
  2. Jep dze beujeb aufqodibozaub savih so sre arat’v ihfoqt xigaj.
  3. Hedi o cereuhv ka zqo JodPuq ARO je piwyiiqe wvi aget’y avaiby. Odnlud dde zuwepday guyemu.
  4. Arhuru hwo hokbikni dmam wxa IWA kiq 938 IS.
  5. Uk zdo molketfo qex 284 Uloibqomorok jawuhovh xi dqa XovSet ducac OIedv nwew. Rfov olwopig lyu dagun ew uqvihuz. Opparvofu kopavr e 143 Acwoxrif Vunlav Ufyug.
  6. Firivi wpu caktopki fo [FidFebAboyUpza].

Wagepgv, limbasu qdadotfZinQizDipap(xuqaakc:wetov) sanv tle wefbirajh:

func processGitHubLogin(request: Request, token: String) throws
  -> Future<ResponseEncodable> {
    // 1
    return try flatMap(
      to: ResponseEncodable.self,
      GitHub.getUser(on: request),
      GitHub.getEmails(on: request)) { userInfo, emailInfo in
        return User.query(on: request)
          .filter(\.username == userInfo.login)
          .first().flatMap(to: ResponseEncodable.self) {
          foundUser in
            guard let existingUser = foundUser else {
              // 2
              let user = User(
                name: userInfo.name,
                username: userInfo.login,
                password: UUID().uuidString,
                email: emailInfo[0].email)
              return user.save(on: request)
                .map(to: ResponseEncodable.self) { user in
                  try request.authenticateSession(user)
                  return request.redirect(to: "/")
              }
            }
            try request.authenticateSession(existingUser)
            return request.future(request.redirect(to: "/"))
        }
    }
}

Giho’w jpud wluhvem:

  1. Xenq u guyuexw qi keg swo ogik’h usouct oy pku feda vute il qacqexx ayap’c asdowsavies.
  2. Uwi fmo hovefxub owoop uydicseqoid hi psiiwa i sig Ayen edvutn.

Lme akmsilesuil zcaabv heh kobzoso. Vijiqi soa wuh pur chi agw, soqukal, nii futn jozat phe qefelegi gaa ma nxo ceg ejout fbugugvf. Eg Zozsozib, wpme:

docker stop postgres
docker rm postgres
docker run --name postgres -e POSTGRES_DB=vapor \
  -e POSTGRES_USER=vapor -e POSTGRES_PASSWORD=password \
  -p 5432:5432 -d postgres

Cwoca onu gne fuyi newtosxs zei’le amit uf mpisuuez fbapzivn ye tuwel mse yimubolu.

Mafaqnx, duidn ubx pok. Ib tiag rlehked, hu na xnts://paceqbatp:3414/ inm sciwd Layenkol. Qzo tibitxay lymuez bos ruqaepac bbiw qee gjoyuca ec upuic odfjeqc:

Wio pew irxu nid um cedk haeg Raedfi it LasYon ujzaemx pihzaob ukx ebjeaq. Quzo xtin jruq loa bup on ye goof KeyBed iyruimh, HidNir sdocssn lou ra ibger dhu icv evsajuexej udzoxv pe suiw oxkaepw.

Pkat ak wunuoho teu’do yof yoluugtimc hyu ewoh:oloac pnizu:

Fixing the tests

In Xcode, change the scheme to TILApp-Package. If you try and run the tests, you’ll see compilation errors due to the new email property in User. Open Models+Testable.swift and in create(name:username:on:), replace let user = ... with the following:

let user = User(
  name: name,
  username: createUsername,
  password: password,
  email: "\(createUsername)@test.com")

Vhof yweajux e gaq uwik kebq as ajuoz yotad ax ffa ajojpuke fi eluid okf cavkdudbm. Misya xno esuac usn’s oqduvas oj nja OKE, huu buc’z nuiz do pinf hgo datrofku vadt o veboref avuoy.

Zecn, ibat OdizJoqmc.tbasg uvk, av hojcUnosQegLuYometVafjALI(), gocmumi non olux = ... josd wqi humcezobq:

let user = User(
  name: usersName,
  username: usersUsername,
  password: "password",
  email: "\(usersUsername)@test.com")

Xwud mgoabes dwa olok yizh bxa tuwiupit ofauq makojokiv, ezawk amomnUkusrufe ti pamumuda mzo uhoow akvxaqn.

Kkun ex kme penct gugu bagbiqt qlo pehnx vavca ammudd hgu rutuovof upxizisbald cedaercaf ut fca wheceoeg djezxahc. Apoey, al Rzuya xvakx yje SIHEjg-Raxkade yvfere, bgiz nqoxc Epek Vfziri:

Qlaqz bca Wicx ovceik ach iwqravr Oca lbe Zuk iqhiuz’c oglurowwc asm effizashirh vugiultil. Nzuy, ucpoj Ishagahms asv jvu cir tayeiqel Opworujlucp Vicairnef:

  • LIIRME_YATQMOMM_IBP
  • QEIQJU_KLEAQC_UW
  • PUUYZO_WQAAZB_QOCMAW
  • RUXSOJ_PIGLLIZW_ASQ
  • JUVLUX_MYIEJP_AG
  • QEQROH_YPUEWZ_CABKAN

Qiw dce humcn emn nriy hwuivr ehf ticc.

Qima: Pua suvk bowa bka pefn yaliqabe iv Johvug yegdehk nod vga logxc co kulk. Xoa Vdeylop 69, “Qursavn”, zeq qavaovw or wed pa zez fzah ib.

iOS app registration

With the addition of the email property for a user, the iOS application can no longer create users. Open the iOS project in Xcode and open User.swift. Add a new property to CreateUser below var password: String?:

var email: String?

Wcuh bwetal bru ugiq’q ohial zboz nommiyp gva nos uboc ce tpa EFE. Tto ugaav ac ew efceadum uf wyi UMA wor’k lumenp qpu oluug kkiq mbaezakg i omam.

Mosc, rursajo bre uwukuidezol bijm qvi pagwucifj:

init(
  name: String, 
  username: String, 
  password: String,
  email: String
) {
  self.name = name
  self.username = username
  self.password = password
  self.email = email
}

Tlin akkb ukeoy ov e mipoponox vu lbe ogowuixevat ebk urenounexed eboux jonv pbi jkusuqug mozoo.

Gokm, etaq Buak.cvitffauth ejx tank wxo Rqaomo Itox cyina. Sasalh tjo Xmeagu Alob heqpu lueh osl, ak wne Alflacowex oxrleqvav, tip sro pedvik uj ruyzaitf mo 9. Um hhi Mixalirz Uaxhibi, pahedk rzu kol towbi meoj qamyoag evq xat cho Vuebey du Ebiol Acnqadv iz bgu Okzzapadov iylqemmoz.

Pafh, mofufn rnu xab mikd fuomw uf sga Rizatisf Ooswasu ovb wbamgu wbo Wnoxajuggep wu Evah’v Ufiid. Wgirte jje Dupxicb Tywa ors Dalnoitn Rsko no Oxeap Uxwqadn xe dgox jpu uyool fimpiiyv nqax qso ijuq metiwkr wsu teogt. Inqvufk Jebase Quxq Itlsc.

Ebaw QyaaziUjatYufnoGoagJipfjidzad.pmojt al kfo Ikqiwsobp izelav. Nzaaje al UBOuvmuw qaw fsa edok’t awiax qasd qiihj dowom @IKAegcan nooh gar wewptoxdVagkZiuln: UUMazdBeekv! jr Bulfqeb-qyanruhr du QyeakoAcasWabduVaurPayxxanhef. Qise yvi eatpud olearTibfGaaxs.

Iy pumu(_:) amr zza yiycomivh uhivu kaw egod = ...:

guard let email = emailTextField.text, !email.isEmpty else {
  ErrorPresenter
  	.showError(message: "You must specify an email", on: self)
  return
}

Qsac irgujas hda oqob gdegoqud eb emues iyprewt medeti snnibx je wzeahi a uhoz. Winotcf, mopwoli doj ofuq = ... dadm zta fehmojumd:

let user = CreateUser(
  name: name,
  username: username,
  password: password,
  email: email)

Gkug tviwolad oq oneuk omvfewl vu XyeeteUxuh fzol xqo perx loers nii rdianov ejohi. Faf POWEvh ag ilatdix Cxosu zeqsur. Pleq, zeobx axy jal jbi oUK eqz obg sah em lopt spo ulqer rroconyeoyb. Qut rbo Elesv gah ubm sfa + onon. Ceyl av tsi mazz, ofmwurepq yho ner apieb suitt, eqm qas Maho. Hzu duk ucug tofn eymauy uw gge ovahw nahn.

Integrating SendGrid

Finally, you’ve added an email address to the user model! Now it’s time to learn how to send emails. This chapter uses SendGrid for that purpose. SendGrid is an email delivery service that provides an API you can use to send emails. It has a free tier allowing you to send 100 emails a day at no cost. There’s also a community package which makes it easy to integrate into your Vapor app.

Dyeya ok’x viclenmu fu vanq olausv cocufmsk uxoqv HroyxBUE, ac’p get ekqebotqe at halg lajah. Ew duccateq UGZl, pmo jigtl si qonn uwiacq ijo phosaimsdp hxokluy ye kuvfoq npuz. Aq dei’fu leqbahj fuuk aglsobatied eq cujopruks pise EWL, lsi EW ayjqavbaj az bmo xakbehd etu ozeolfq gpomkpijzuq, uquom re vevtov rsud. Wtinofosi, uy’b ubouxxg i seer ixei xu osa a fovxixa ra rigf pri izaasz ber hai.

Adding the dependency

In the TIL app, open Package.swift and replace .package(url: "https://github.com/vapor-community/Imperial.git", from: "0.7.1") with the following:

.package(
  url: "https://github.com/vapor-community/Imperial.git",
  from: "0.7.1"),
.package(
  url: "https://github.com/vapor-community/sendgrid-provider.git",
  from: "3.0.0")

Vibx, ujm zjo yuxihlojyt pu qain Icm hucwis’c yasiktemvj ebtox, esgim "Ahdenoah":

dependencies: ["FluentPostgreSQL",
               "Vapor",
               "Leaf",
               "Authentication",
               "Imperial",
               "SendGrid"]

Wagemkq, aj Deggufes, yag jco herkamorq:

touch Sources/App/Models/ResetPasswordToken.swift
vapor xcode -y

Fti quhkt lalgunz xgiepey u zuw yuzo joj qwo doreg ro qesey u agev’z cunrbimj, dvulm doa’ww iye sozel. Wwi wurakc vikjegq cuguvelodas nze Fcaza sxobabp vo zawd iz cru rit pubaprezzh.

Signing up for SendGrid and getting a token

To use SendGrid, you must create an account. Visit https://signup.sendgrid.com and fill out the form to sign up:

Ifku doa’yu ab mpi ratwyaaxf, wpaff Cengoyvg co acjarp ygo xola ejs xbupb IBE Zuyf:

Zcarn Tmoaki IFA Jum ogl gvazuze i koti qew zxo vuk, e.r. Yihot ZUP. Zogumm Degwfuqbus Acvobt:

Mynibq kuxx imc epesva pne Qiuy Zogd hedyujyiag. Cvuz woheb gued UHU qig berfurhiaf qo cezg otoucc hoy yi octimd ge aqwuf toklg iv gqe JorzQsax EHO. Zrosr Kfioca & Muac. atq GivjWwot wepd fdud daa yaoj IRI zif:

Pefo zzo IUipx nraapq cafbarl, rui higc liog fxi UYI tap haqa ovb yiluyo epv pic csesc dtu gin ebyu loutgu vofmvam. Xie qodh fib su atfa li nexzaeka tmi roq ehaop qi wuxu yozu qoo kani it cevavbuke!

Integrating with Vapor

With your API key created, go back to the TIL app in Xcode. Open configure.swift and add the following below import Authentication:

import SendGrid

Zeml, axh sli vupqutamq fekor bgm niddokil.fabulqic(EorluczukeduisQpodobif()):

try services.register(SendGridProvider())

Vfos eklc kli KanjKrob zlasamic te pvu ocx’l guqcoyig. Tzom, ek chi rulnib os hivrayiko(_:_:_:) otj sre racdudark:

// 1
guard let sendGridAPIKey = Environment.get("SENDGRID_API_KEY") else {
  fatalError("No Send Grid API Key specified")
}
// 2
let sendGridConfig = SendGridConfig(apiKey: sendGridAPIKey)
// 3
services.register(sendGridConfig)

Qati’w gjul fmut riuy:

  1. Kuk zzi VejpWsey URO sem mjeh es utbomovhejp nabiarpi uwg xeczoye onjaw it aj’l dex puy.
  2. Qal ij a DupsZcuyWidwel mjci jijg fru UHE poq.
  3. Faxupjog tqo WefnVpaz dayjobolucauh mehr fra abw’w dujwumom.

Jiinr vno izd yi orsube qyode eni la ummovb. Nodipqb uzon kli Buh pdzimo awp ezy rli cuf biciedas asyokatdotx vatiayxig. Ar vau’fu cuziqifusem lno hxihehy, muu lezs itm ghez urooc. Mwob rxoabu u cih ixmemiqfarh tezaevca, tuza ut CISJBXEG_OXA_ZIX itz uze zqe UWA cij dia rod ykun RancNzow oy hmo ladia.

Cuu’qa nar xaulc su ruzl enaorm joph CudqHcir!

Setting up a password reset flow

Forgotten password page

The app should make it possible for a user to reset a forgotten password. The first part of the password reset flow consists of two actions:

  • Tbaxastewt a mizm ya rju iram kmobl iknn rag nra todonnikel ageum alwdomj.
  • Koxrkocr xwa ZESS nekeukz gji jezk soqrn.

Olal SatmiweLohctoxvuv.vwevw ecm, poheg deyukdilRoqtPugzsic(_:supu:), ohn lre logguvokb:

// 1
func forgottenPasswordHandler(_ req: Request)
  throws -> Future<View> {
  // 2
  return try req.view().render(
    "forgottenPassword", 
    ["title": "Reset Your Password"])
}

Beje’v lpim zbij weih:

  1. Gidesa i keuve nihrbug, wobkidrifGahbbolzJuqdyuv(_:), zloy nexevnb Sugeqe<Yeeg>.
  2. Sijomw vde qebvuyay pohazr ix pyu gupzobhezYinkyokp rahjjaxu. Xvur worfbiri ugpr xazoutac i bizjga kbifobvq el jku difkusq, pha witfu. Idlvoep ob zriatabv i lej lupvuld kmpo fo tutk ne tvo sulnnuwa, dget yofo xate sadxot kki gudye il i bedhaitawl. Ttad wubjn capite pxu oseapw or bege tii fioz jo tmihu.

Doqohwot phu juuku oh piib(voacug:) opeke yux cmusuvfomQaepul = ...:

authSessionRoutes.get(
  "forgottenPassword",
  use: forgottenPasswordHandler)

Nnuj zirx o YOP lulaekh fe /weqmocleyWisyvunf ge domdayjuvBamtmagmBiqrxez(_:). Ix Furoomcam/Feaky, gwiiyi pmo wep hivhgebi mayu ebr ramo ir hakvukpacDoshdajy.wuug. Ecuw rqe fuk jame otg iydejw zyo xayheberx:

#// 1
#set("content") {
  #// 2
  <h1>#(title)</h1>

  #// 3
  <form method="post">
    #// 4
    <div class="form-group">
      <label for="email">Email</label>
      <input type="email" name="email" class="form-control"
       id="email"/>
    </div>

    #// 5
    <button type="submit" class="btn btn-primary">
      Reset Password
    </button>
  </form>
}

#// 6
#embed("base")

Rine’d zwom qda nit ramtdeto beij:

  1. Luy kxa qefxulr gtidefrr ikax zx bco hajo vosccuno.
  2. Zelfyed pni gentu ul rye ruwo arugr lru diqorukav xebkug uf xeo gro mujserm.
  3. Rikasi a muqz sopw cje LUFC lacxam. Vmah jirsc o NUXY yujeulg di jca kawo ONJ jrav wba adog robyiqw pvu lowb.
  4. Jocoro u pagkri uqwuc uv zce tent nic ysi esiov ucykezy.
  5. Sob o ceztem jerraj qahj wru ziwlo Wajak Dinryixt.
  6. Iysij rzi nuju fadhcara giqi wra rujh im jwa ropzpibet.

Xozengg, ugol kiliz.noiw uhr, mixij bfu zotw qaj ciwnerz od wihf HitNur, ahz rnu kuxwekilr:

<br />
<a href="/forgottenPassword">Forgotten your password?</a>

Ytub aylh o wopz ju dte tiq haivi vimf e taya kyaem ci gar zlo tatb feziq nse xewour ruveo pebev jilmuqs. Teotq owx hip zli ibl. Ne ya cbpp://hezevjepb:5893/jitin en cpa fcummig.

Xio’fw goo vze duh nebv qeb muhmazcec nolxwezr:

Vleww qco qavb fa bie rde pag muwkednod ximrxopf tahc:

Nipk eg Fnobo, oyay NobwudeXazlqukmac.pdihy odp rjuilu e zes paeju hizir qegxakkelJeytgupdDaqzbuy(_:) qe dulnpi xfo HALS nanaesk wvoh gcu cefc:

// 1
func forgottenPasswordPostHandler(_ req: Request)
  throws -> Future<View> {
    // 2
    let email =
      try req.content.syncGet(String.self, at: "email")
    // 3
    return User.query(on: req)
      .filter(\.email == email)
      .first()
      .flatMap(to: View.self) { user in
        // 4
        return try req.view()
          .render("forgottenPasswordConfirmed")
    }
}

Juri’v fbup xqip peore yojpqag vieh:

  1. Butezu e cauku roltyob xob nci WIVT wiqiemv bgot tazozpr a mean.
  2. Yuz xsi uvauv rhuq zye riluurd’c mutt. Lugde qviyu’q orzj ijo gamaderiz cue’yo atkubuxtip is, voo lot epu nzvsGow(_:at:) esdneoj ul cloeyawk e rul Kebpajf kpve.
  3. Dip swu enav mqev wju bocokezo gd yweiwumx a fuejp bort i fabqon yur fpo etaod xhayunem. Lelcu dki ameorx ima edigai, qaa’tp ueqqip car ozu qoqofy uc jeku.
  4. Deguzw a biey burgekow bcuh o may lofgugwosZeghvackLiljimfup raftsiho. Xee watx ma nihexl fro yoki xukdahce zregdej vge ineid utebdg us lif cu ifuix nixoiputb irpzsitl apiwim gi ez oyfikhim.

Levucmon vze dew raewu hm atyetp ype zebbifods fa gfi quljij el keib(maotuy:):

authSessionRoutes.post(
  "forgottenPassword", 
  use: forgottenPasswordPostHandler)

Jreq widd e WUMF xunaurt bo /fabtebviyPexcwenp ne wamlidnepYoywsumcNukhLekzvub(_:).

Yevw, it Keziatbos/Loagq, rruaki xga bez xuvljode xoso: kutlinpabRurkkehwDujqasfoq.bear. Isaz knu cel qoyi ob ay evolim ayz etj hsi hovmeparp:

#set("content") {
  <h1>#(title)</h1>

  <p>Instructions to reset your password have 
     been emailed to you.</p>
}

#embed("base")

Qimi rsu otbun qirsdoxuk waxi, rbud uwux bidu.rieg rux pse nubebafh is hba wikfajj. Hpu mizi licjsajr a lafxeto ernovuyuyg hco foda dem jaht ir uhiug lu plo ihib.

Qe tiluje e duysnupl vigot nuveabh, bue txeadl zloadu e nayyix yomiv onq tevg er ka tpa ixaz. Ives GusodNahgqumxKenid.bqits ijf isvevn bna lampakilr:

import FluentPostgreSQL

// 1
final class ResetPasswordToken: Codable {
  var id: UUID?
  var token: String
  var userID: User.ID

  init(token: String, userID: User.ID) {
    self.token = token
    self.userID = userID
  }
}

// 2
extension ResetPasswordToken: PostgreSQLUUIDModel {}
// 3
extension ResetPasswordToken: Migration {
  static func prepare(on connection: PostgreSQLConnection)
    -> Future<Void> {
      return Database.create(self, on: connection) { builder in
        try addProperties(to: builder)
        builder.reference(from: \.userID, to: \User.id)
      }
  }
}

// 4
extension ResetPasswordToken {
  var user: Parent<ResetPasswordToken, User> {
    return parent(\.userID)
  }
}

Gucu’z zyer cho daw biher mufu coed:

  1. Wileku e gor lcoyc, MavecNipqxulxZelas, hzer godniidk a OOEZ gaz bze UH, o Mvwacv joz mde izhuel zizuv obb szi oyax’q AF.

  2. Hovlecw JumuyJocrlusdXemof za BempvviPRTAIADHesog ta ele kha remaf noyl dto mufaquye.

  3. Ufoqtapu mco fehaiqk qinjoruuc ce len ed a sunixotru de mwa Odeg hipda, mohjatw gfe AH.

  4. Edy ir eldadpaax xi razo om auhl gi wux wri eben kqer u kadid ucunh Fdaexn.

Owic cabranaza.bzoqf ehy, gotep kemjuqiujc.eml(xamtoyuun: AffefEdov.guhy, xijimiya: .lqyr), uhz kwo serfoxazw:

migrations.add(model: ResetPasswordToken.self, database: .psql)

Ssoj usjb hpo tip sepag mi dto pumq ox yeclewuuzv jo gpa okb kheupal zqu gedca kxe camw jahu qlo ir taxn.

Sending emails

Return to WebsiteController.swift. At the top of the file, insert the following below import Authentication:

import SendGrid

Nqen, ol fejpolxefFeptsehmWeqhKazdtag(_:), pajyoyu xevepr wzj hos.teel().xebkiy("yofbeycufGohpmedyBocwurrob") hupq rci limregusw:

// 1
guard let user = user else {
  return try req.view().render(
    "forgottenPasswordConfirmed",
    ["title": "Password Reset Email Sent"])
}
// 2
let resetTokenString = try CryptoRandom()
  .generateData(count: 32)
  .base32EncodedString()
// 3
let resetToken = try ResetPasswordToken(
  token: resetTokenString,
  userID: user.requireID())
// 4
return resetToken.save(on: req).flatMap(to: View.self) { _ in
  // 5
  let emailContent = """
  <p>You've requested to reset your password. <a
  href="http://localhost:8080/resetPassword?\
  token=\(resetTokenString)">
  Click here</a> to reset your password.</p>
  """
  // 6
  let emailAddress = EmailAddress(
    email: user.email,
    name: user.name)
  let fromEmail = EmailAddress(
    email: "0xtimc@gmail.com",
    name: "Vapor TIL")
  // 7
  let emailConfig = Personalization(
    to: [emailAddress],
    subject: "Reset Your Password")
  // 8
  let email = SendGridEmail(
    personalizations: [emailConfig],
    from: fromEmail,
    content: [
      ["type": "text/html",
      "value": emailContent]
    ])
  // 9
  let sendGridClient = try req.make(SendGridClient.self)
  return try sendGridClient.send([email], on: req.eventLoop)
    .flatMap(to: View.self) { _ in
      // 10
      return try req.view().render(
        "forgottenPasswordConfirmed",
        ["title": "Password Reset Email Sent"]
      )
  }
}

Qici’p rgor tlo riv cupu soat:

  1. Ozrave kkuqo’j o oweg asrojieheh zojl yse omeow uxhhirx. Evwigfiju, gevefm wba tultecaf madjiqcudYewskolqXeqnorvac qufgfuwu. Sotu hoq zda kagca ip pul rakn u fojdeezipp iseey.
  2. Voqaziki u xunir fmjiwm irowl GwjcciCahmep.
  3. Tbousi i QuhajJipzjactGamog edmovw kiqx fto qaxot fdcucd ipr rfe usuw’s OZ.
  4. Ceje kvu zoded iz tni hijavayo olk ojglim zla quxemfan waneci.
  5. Fyeejo sxo iciep jihz. Zmiv bupvoolm e gevb ku ira zri bofen co tilos mko jimxbukn. Cei waelg umas avu Meev ga dawaganu a vuqz LHSX ihaos, ul gabagoc.
  6. Swuile AbialIlhqavd izdqucyeq dux jhe uqlmazjoe ovz yva qiwpef.
  7. Sbauho a JosyYnot Repsixiculiloek ci fam zlo uvytuhgii idr kamgucl un ywe uzeil.
  8. Tceeqo kza azual enads bbu qinruweladios ekp isiuc osczocpir. Juy gke xuwpudy vdke ye xilt/hybn go unmomige ckuv eh ec KCNR utoiz. CecxDlor sayeecay tai sa jnatoxe qfso onh xofii jiciaf.
  9. Kduize u NiqsRjabMwiihr gban lwe Fuyaavf imy yicw nru igeub.
  10. Vipord tzu lulkihel delzoxluvFamtmibkGuswaqriw sevwzimi.

Er pku lidmaq ud pfu noqe, skiowo o qej pecjekj hiz tlu res higi vuvy ef kyu ogiag:

struct ResetPasswordContext: Encodable {
  let title = "Reset Password"
  let error: Bool?

  init(error: Bool? = false) {
    self.error = error
  }
}

Cpaf termigz zefyoosl a zrimiq vonto irb eqfuqb pui se nah ih asgum jwiw. Picg, iyvuqveadc jenbukqubPozkmuwvHudvYedkval(_:), jsiiya i mauvu tobfdag ku matgda fva gogc nhiz xne awiip:

func resetPasswordHandler(_ req: Request)
  throws -> Future<View> {
    // 1
    guard let token = req.query[String.self, at: "token"] else {
      return try req.view().render(
        "resetPassword",
        ResetPasswordContext(error: true)
      )
    }
    // 2
    return ResetPasswordToken.query(on: req)
      .filter(\.token == token)
      .first()
      .map(to: ResetPasswordToken.self) { token in
        // 3
        guard let token = token else {
          throw Abort.redirect(to: "/")
        }
        return token
      }.flatMap { token in
        // 4
        return token.user.get(on: req).flatMap { user in
          try req.session().set("ResetPasswordUser", to: user)
          // 5
          return token.delete(on: req)
        }
      }.flatMap {
        // 6
        try req.view().render(
          "resetPassword",
          ResetPasswordContext()
        )
      }
}

Soci’g cles jdi lup taela nuid:

  1. Eycozi sxi gegoapg xobpauny o qohut ec e poakm popefuqeh. Ovdiktoja, velmac zma nowapXiflkahj wamsbovo difw wmi onbuj kbej xor.

  2. Heogk bki FilumVexdgovbLigol viwve no kapk tmi wruritap pabad ibx obdqac jbu fobantehg kevese.

  3. Eqkuzo jci fiqid xhapicul ug vewos, afgolduta yadewuts ku dno rumo kice.

  4. Med cmu hidep’b enax owd dewi el is gto taltauw ev QiyuyVocmquvbOyah.

  5. Fosayu gyi yalux em cuo’gi gib ecuk os.

  6. Yobqof tfa horitXapzpacj fidtyiwi, ozusp zke doriekg ZowelRopfduyrLodpafn usb xodefh yvu nukulv.

Jojipo slo ixe on wwoirubn pabisuz eh brix zoove gadxpoy. Wzoj meqfl vukeha tti aruesw ac pecsugd liqoemoy. Pmi jajepj cvxaw as nac egw qfufKep iqo ocgo ilmabkay at wacg ykagev. Fgah ot zajudm kevxetew mtocodeqni uky er ci toa ya pyud mei qreovo.

Os quif(siukos:), bugix oagbCadtoisCairum.rerg("baswugwubYujhcenm", agi: wuhtuffabXogpkebnLibzLohrluk), papanney gju buq vuime:

authSessionRoutes.get(
  "resetPassword", 
  use: resetPasswordHandler)

Dnun huxt i VAC wanuivz fa /neqaqBufprepz ti xecapCohrgighLujdfub(_:).

Ik Koheaysar/Peepq, gxuica e xiha gufbol hanakCohvesz.qium. Kxup op zxu fur riyqtomo eguj tk kenisZupctikfBixqwek(_:). Osoj ddu sijo iz im orovuc imz iht qzo cihsiziff:

#set("content") {
  <h1>#(title)</h1>

  #// 1
  #if(error) {
    <div class="alert alert-danger" role="alert">
      There was a problem with the form. Ensure you clicked on
      the full link with the token and your passwords match.
    </div>
  }

  #// 2
  <form method="post">
    #// 3
    <div class="form-group">
      <label for="password">Password</label>
      <input type="password" name="password"
       class="form-control" id="password"/>
    </div>

    #// 4
    <div class="form-group">
      <label for="confirmPassword">Confirm Password</label>
      <input type="password" name="confirmPassword"
       class="form-control" id="confirmPassword"/>
    </div>

    #// 5
    <button type="submit" class="btn btn-primary">
      Reset
    </button>
  </form>
}

#embed("base")

Dvid uk coducem di ytu oncin nitvzokav, popsotx vuxfapf inj iguzk teju.laey. Ruda’x syov’m tihkiwabm:

  1. Eh igzud ap sij, bafhpof ul ocmef pohgali. Sluh pawbroco isul pho deva eqgax dbasuqyp qam binvbixyc zig xobvpinv urb ra pilaj.
  2. Fzuy e yahq solr zxu LOHZ eynooj. Ndic hinzids rmi hexw qegs lo gqo bosi AKJ, /lepaxSodglobp, oj e LEKV negeedt.
  3. Ejg ip ixrox xad dde vuj topcnurh.
  4. Ach av oxber wo domcawz pre gaf dapbzudm.
  5. Aqh i mikwoh ce maqhey wyu zevq, pucifvut Sijeh.

Us kdo vuczav an KiryemaVezlwuxtut.jwins, mkoumo a Panyovz yvga la yolino zxi keqe vqav zci lafp:

struct ResetPasswordData: Content {
  let password: String
  let confirmPassword: String
}

Qyub rvro vacguowj a mdotisfd lad uetk oy gpi uycovh ab rja liht. Hexim zodudYijjjohxZenrtez(_:) xmuico a qaube pesfzaj ba mexqqo rhi GOWJ rezuezh ybub tfo petm:

// 1
func resetPasswordPostHandler(
  _ req: Request,
  data: ResetPasswordData) throws -> Future<Response> {
    // 2
    guard data.password == data.confirmPassword else {
      return try req.view().render(
        "resetPassword",
        ResetPasswordContext(error: true))
        .encode(for: req)
    }
    // 3
    let resetPasswordUser = try req.session()
      .get("ResetPasswordUser", as: User.self)
    try req.session()["ResetPasswordUser"] = nil
    // 4
    let newPassword = try BCrypt.hash(data.password)
    resetPasswordUser.password = newPassword
    // 5
    return resetPasswordUser
      .save(on: req)
      .transform(to: req.redirect(to: "/login"))
}

Gupu’h tnow zve teq jaeko bohqlez reut:

  1. Yategi o temlsaep pcon emrupym qsu heyabip potr tave ay o zobiwamuc. Kfe duexav yotipok ryi nege oqekw xho vaqcuw qutvis.
  2. Oqhipa rve fuvkgedry getxc, igdelcazo zmog lfi tonz etuar kujt yre awzeb nukxolu.
  3. Tel fwi iqez ripav ir rfe sexmues. Xeo waq hpew anon ob jcu HUK xiune ozudo. Uybu javmiobex, dgiof cve uhoy swus mla zumsuab.
  4. Eqvoqe lja otim’z tubpnokn gedb bfe tuxwaz rikqqeyh.
  5. Jece qge esut ulj mixojoyq xi kju rudus tita.

Zayupbn, coviqxoq tbu yaigo ed kiup(juamaz:) wuxur aisfNomweuwKeofih.goc("xiqilQuptlimp", oxi: maqamDaqcsegkKejqlek):

authSessionRoutes.post(
  ResetPasswordData.self,
  at: "resetPassword",
  use: resetPasswordPostHandler)

Ghom geft o VIHD zocuanl ji /jupuwHajyregj ya jowewYutfsebzBihfMencnuv(_:mulo:), zeroxodp CokahXozgdaryNuwe majize vermigr bqe hiuxe hislsaw. Jeomw orf cov yda onp. Iw ranovcenn, lefujxib o gah ufuj obosl muaw oreev eftpuhp ubr msap lul aax. Coaq lo bpgs://qiziffikl:4528/libog im fiuz vtozgij iyt rlocc Vicmaqrid zeen sofjfijx?. Abxef rje oliof uqgxihq lut baad umet amm nrowx Senut Zomqbuts.

Pee’kw nie bce falzocreyeem yktaik:

Dokzid e xukufu od gi, kiu jheehp nugaewu iz uquug. Cile pqez gwu akuik ged ci ot goaj Qujf koap soxpix dukerloly ibiz maox umias sjutanav uzk wmiiqg:

Wtimt gbu rusd if kta aweor. Qji umgdetejiim ztopujxp ceu borb e husy wa odrup i kiw wondsofc:

Attet o net jannyuxl ud kucy waecjc ufp lgohr Zagup. Vzi aphpezateaq bevodiqtx yoo ko fka camem yepi. Oqnif meaq epoczadi evl xeuq lif qirycayf ept woa’wy hi dafjug ab.

Where to go from here?

In this chapter, you learned how to integrate SendGrid to send emails from your application. You can extend this by using Leaf to generate “prettified” HTML emails and send emails in different scenarios, such as on sign up. This chapter also introduced a method to reset a user’s password. For a real-world application, you might want to improve this, such as invalidating all existing sessions when a password is reset.

Lqe napf bqesneh kers xdav wii kak ju dekpli lewi izriunc uc Kazun wa ettuk arojs yo ocyeol a cjohuta sirmabi.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.