builder.go 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418
  1. package login
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "io/fs"
  7. "net/http"
  8. "net/url"
  9. "reflect"
  10. "strings"
  11. "time"
  12. "github.com/golang-jwt/jwt/v4"
  13. "github.com/markbates/goth"
  14. "github.com/markbates/goth/gothic"
  15. "github.com/pquerna/otp"
  16. "github.com/pquerna/otp/totp"
  17. "github.com/qor5/web"
  18. "github.com/qor5/x/i18n"
  19. h "github.com/theplant/htmlgo"
  20. "golang.org/x/text/language"
  21. "gorm.io/gorm"
  22. )
  23. var (
  24. ErrUserNotFound = errors.New("user not found")
  25. ErrPasswordChanged = errors.New("password changed")
  26. ErrWrongPassword = errors.New("wrong password")
  27. ErrUserLocked = errors.New("user locked")
  28. ErrUserGetLocked = errors.New("user get locked")
  29. ErrWrongTOTPCode = errors.New("wrong totp code")
  30. ErrTOTPCodeHasBeenUsed = errors.New("totp code has been used")
  31. ErrEmptyPassword = errors.New("empty password")
  32. ErrPasswordNotMatch = errors.New("password not match")
  33. )
  34. type HomeURLFunc func(r *http.Request, user interface{}) string
  35. type HookFunc func(r *http.Request, user interface{}, extraVals ...interface{}) error
  36. type Provider struct {
  37. Goth goth.Provider
  38. Key string
  39. Text string
  40. Logo h.HTMLComponent
  41. }
  42. type CookieConfig struct {
  43. Path string
  44. Domain string
  45. SameSite http.SameSite
  46. }
  47. type TOTPConfig struct {
  48. Issuer string
  49. }
  50. type RecaptchaConfig struct {
  51. SiteKey string
  52. SecretKey string
  53. }
  54. type Builder struct {
  55. secret string
  56. providers []*Provider
  57. authCookieName string
  58. authSecureCookieName string
  59. continueUrlCookieName string
  60. // seconds
  61. sessionMaxAge int
  62. cookieConfig CookieConfig
  63. totpEnabled bool
  64. totpConfig TOTPConfig
  65. recaptchaEnabled bool
  66. recaptchaConfig RecaptchaConfig
  67. autoExtendSession bool
  68. maxRetryCount int
  69. noForgetPasswordLink bool
  70. i18nBuilder *i18n.Builder
  71. // Common URLs
  72. homePageURLFunc HomeURLFunc
  73. loginPageURL string
  74. LogoutURL string
  75. // TOTP URLs
  76. validateTOTPURL string
  77. totpSetupPageURL string
  78. totpValidatePageURL string
  79. // OAuth URLs
  80. oauthBeginURL string
  81. oauthCallbackURL string
  82. oauthCallbackCompleteURL string
  83. // UserPass URLs
  84. passwordLoginURL string
  85. resetPasswordURL string
  86. resetPasswordPageURL string
  87. changePasswordURL string
  88. changePasswordPageURL string
  89. forgetPasswordPageURL string
  90. sendResetPasswordLinkURL string
  91. resetPasswordLinkSentPageURL string
  92. loginPageFunc web.PageFunc
  93. forgetPasswordPageFunc web.PageFunc
  94. resetPasswordLinkSentPageFunc web.PageFunc
  95. resetPasswordPageFunc web.PageFunc
  96. changePasswordPageFunc web.PageFunc
  97. totpSetupPageFunc web.PageFunc
  98. totpValidatePageFunc web.PageFunc
  99. beforeSetPasswordHook HookFunc
  100. afterLoginHook HookFunc
  101. afterFailedToLoginHook HookFunc
  102. afterUserLockedHook HookFunc
  103. afterLogoutHook HookFunc
  104. afterConfirmSendResetPasswordLinkHook HookFunc
  105. afterResetPasswordHook HookFunc
  106. afterChangePasswordHook HookFunc
  107. afterExtendSessionHook HookFunc
  108. afterTOTPCodeReusedHook HookFunc
  109. afterOAuthCompleteHook HookFunc
  110. db *gorm.DB
  111. userModel interface{}
  112. snakePrimaryField string
  113. tUser reflect.Type
  114. userPassEnabled bool
  115. oauthEnabled bool
  116. sessionSecureEnabled bool
  117. }
  118. func New() *Builder {
  119. r := &Builder{
  120. authCookieName: "auth",
  121. authSecureCookieName: "qor5_auth_secure",
  122. continueUrlCookieName: "qor5_continue_url",
  123. homePageURLFunc: func(r *http.Request, user interface{}) string {
  124. return "/"
  125. },
  126. loginPageURL: "/auth/login",
  127. LogoutURL: "/auth/logout",
  128. validateTOTPURL: "/auth/2fa/totp/do",
  129. totpSetupPageURL: "/auth/2fa/totp/setup",
  130. totpValidatePageURL: "/auth/2fa/totp/validate",
  131. oauthBeginURL: "/auth/begin",
  132. oauthCallbackURL: "/auth/callback",
  133. oauthCallbackCompleteURL: "/auth/callback-complete",
  134. passwordLoginURL: "/auth/userpass/login",
  135. resetPasswordURL: "/auth/do-reset-password",
  136. resetPasswordPageURL: "/auth/reset-password",
  137. changePasswordURL: "/auth/do-change-password",
  138. changePasswordPageURL: "/auth/change-password",
  139. forgetPasswordPageURL: "/auth/forget-password",
  140. sendResetPasswordLinkURL: "/auth/send-reset-password-link",
  141. resetPasswordLinkSentPageURL: "/auth/reset-password-link-sent",
  142. sessionMaxAge: 60 * 60,
  143. cookieConfig: CookieConfig{
  144. Path: "/",
  145. Domain: "",
  146. SameSite: http.SameSiteStrictMode,
  147. },
  148. autoExtendSession: true,
  149. maxRetryCount: 5,
  150. totpEnabled: true,
  151. totpConfig: TOTPConfig{
  152. Issuer: "QOR5",
  153. },
  154. }
  155. i18nB := i18n.New()
  156. i18nB.SupportLanguages(language.English, language.SimplifiedChinese, language.Japanese)
  157. r.I18n(i18nB)
  158. vh := r.ViewHelper()
  159. r.loginPageFunc = defaultLoginPage(vh)
  160. r.forgetPasswordPageFunc = defaultForgetPasswordPage(vh)
  161. r.resetPasswordLinkSentPageFunc = defaultResetPasswordLinkSentPage(vh)
  162. r.resetPasswordPageFunc = defaultResetPasswordPage(vh)
  163. r.changePasswordPageFunc = defaultChangePasswordPage(vh)
  164. r.totpSetupPageFunc = defaultTOTPSetupPage(vh)
  165. r.totpValidatePageFunc = defaultTOTPValidatePage(vh)
  166. return r
  167. }
  168. func (b *Builder) Secret(v string) (r *Builder) {
  169. b.secret = v
  170. return b
  171. }
  172. func (b *Builder) CookieConfig(v CookieConfig) (r *Builder) {
  173. b.cookieConfig = v
  174. return b
  175. }
  176. // Google reCAPTCHA.
  177. func (b *Builder) Recaptcha(enable bool, config ...RecaptchaConfig) (r *Builder) {
  178. b.recaptchaEnabled = enable
  179. if len(config) > 0 {
  180. b.recaptchaConfig = config[0]
  181. }
  182. if enable {
  183. if b.recaptchaConfig.SiteKey == "" {
  184. panic("SiteKey is empty")
  185. }
  186. if b.recaptchaConfig.SecretKey == "" {
  187. panic("SecretKey is empty")
  188. }
  189. }
  190. return b
  191. }
  192. func (b *Builder) OAuthProviders(vs ...*Provider) (r *Builder) {
  193. if len(vs) == 0 {
  194. return b
  195. }
  196. b.oauthEnabled = true
  197. b.providers = vs
  198. var gothProviders []goth.Provider
  199. for _, v := range vs {
  200. gothProviders = append(gothProviders, v.Goth)
  201. }
  202. goth.UseProviders(gothProviders...)
  203. return b
  204. }
  205. func (b *Builder) AuthCookieName(v string) (r *Builder) {
  206. b.authCookieName = v
  207. return b
  208. }
  209. func (b *Builder) HomeURLFunc(v HomeURLFunc) (r *Builder) {
  210. b.homePageURLFunc = v
  211. return b
  212. }
  213. func (b *Builder) LoginPageURL(v string) (r *Builder) {
  214. b.loginPageURL = v
  215. return b
  216. }
  217. func (b *Builder) ResetPasswordPageURL(v string) (r *Builder) {
  218. b.resetPasswordPageURL = v
  219. return b
  220. }
  221. func (b *Builder) ChangePasswordPageURL(v string) (r *Builder) {
  222. b.changePasswordPageURL = v
  223. return b
  224. }
  225. func (b *Builder) ForgetPasswordPageURL(v string) (r *Builder) {
  226. b.forgetPasswordPageURL = v
  227. return b
  228. }
  229. func (b *Builder) ResetPasswordLinkSentPageURL(v string) (r *Builder) {
  230. b.resetPasswordLinkSentPageURL = v
  231. return b
  232. }
  233. func (b *Builder) TOTPSetupPageURL(v string) (r *Builder) {
  234. b.totpSetupPageURL = v
  235. return b
  236. }
  237. func (b *Builder) TOTPValidatePageURL(v string) (r *Builder) {
  238. b.totpValidatePageURL = v
  239. return b
  240. }
  241. func (b *Builder) LoginPageFunc(v web.PageFunc) (r *Builder) {
  242. b.loginPageFunc = v
  243. return b
  244. }
  245. func (b *Builder) ForgetPasswordPageFunc(v web.PageFunc) (r *Builder) {
  246. b.forgetPasswordPageFunc = v
  247. return b
  248. }
  249. func (b *Builder) ResetPasswordLinkSentPageFunc(v web.PageFunc) (r *Builder) {
  250. b.resetPasswordLinkSentPageFunc = v
  251. return b
  252. }
  253. func (b *Builder) ResetPasswordPageFunc(v web.PageFunc) (r *Builder) {
  254. b.resetPasswordPageFunc = v
  255. return b
  256. }
  257. func (b *Builder) ChangePasswordPageFunc(v web.PageFunc) (r *Builder) {
  258. b.changePasswordPageFunc = v
  259. return b
  260. }
  261. func (b *Builder) TOTPSetupPageFunc(v web.PageFunc) (r *Builder) {
  262. b.totpSetupPageFunc = v
  263. return b
  264. }
  265. func (b *Builder) TOTPValidatePageFunc(v web.PageFunc) (r *Builder) {
  266. b.totpValidatePageFunc = v
  267. return b
  268. }
  269. func (b *Builder) wrapHook(v HookFunc) HookFunc {
  270. if v == nil {
  271. return nil
  272. }
  273. return func(r *http.Request, user interface{}, extraVals ...interface{}) error {
  274. if user != nil && GetCurrentUser(r) == nil {
  275. r = r.WithContext(context.WithValue(r.Context(), UserKey, user))
  276. }
  277. return v(r, user, extraVals...)
  278. }
  279. }
  280. // extra vals:
  281. // - password
  282. func (b *Builder) BeforeSetPassword(v HookFunc) (r *Builder) {
  283. b.beforeSetPasswordHook = b.wrapHook(v)
  284. return b
  285. }
  286. func (b *Builder) AfterLogin(v HookFunc) (r *Builder) {
  287. b.afterLoginHook = b.wrapHook(v)
  288. return b
  289. }
  290. // extra vals:
  291. // - login error
  292. func (b *Builder) AfterFailedToLogin(v HookFunc) (r *Builder) {
  293. b.afterFailedToLoginHook = b.wrapHook(v)
  294. return b
  295. }
  296. func (b *Builder) AfterUserLocked(v HookFunc) (r *Builder) {
  297. b.afterUserLockedHook = b.wrapHook(v)
  298. return b
  299. }
  300. func (b *Builder) AfterLogout(v HookFunc) (r *Builder) {
  301. b.afterLogoutHook = b.wrapHook(v)
  302. return b
  303. }
  304. // extra vals:
  305. // - reset link
  306. func (b *Builder) AfterConfirmSendResetPasswordLink(v HookFunc) (r *Builder) {
  307. b.afterConfirmSendResetPasswordLinkHook = b.wrapHook(v)
  308. return b
  309. }
  310. func (b *Builder) AfterResetPassword(v HookFunc) (r *Builder) {
  311. b.afterResetPasswordHook = b.wrapHook(v)
  312. return b
  313. }
  314. func (b *Builder) AfterChangePassword(v HookFunc) (r *Builder) {
  315. b.afterChangePasswordHook = b.wrapHook(v)
  316. return b
  317. }
  318. // extra vals:
  319. // - old session token
  320. func (b *Builder) AfterExtendSession(v HookFunc) (r *Builder) {
  321. b.afterExtendSessionHook = b.wrapHook(v)
  322. return b
  323. }
  324. func (b *Builder) AfterTOTPCodeReused(v HookFunc) (r *Builder) {
  325. b.afterTOTPCodeReusedHook = b.wrapHook(v)
  326. return b
  327. }
  328. // user is goth.User
  329. func (b *Builder) AfterOAuthComplete(v HookFunc) (r *Builder) {
  330. b.afterOAuthCompleteHook = b.wrapHook(v)
  331. return b
  332. }
  333. // seconds
  334. // default 1h
  335. func (b *Builder) SessionMaxAge(v int) (r *Builder) {
  336. b.sessionMaxAge = v
  337. return b
  338. }
  339. // extend the session if successfully authenticated
  340. // default true
  341. func (b *Builder) AutoExtendSession(v bool) (r *Builder) {
  342. b.autoExtendSession = v
  343. return b
  344. }
  345. // default 5
  346. // MaxRetryCount <= 0 means no max retry count limit
  347. func (b *Builder) MaxRetryCount(v int) (r *Builder) {
  348. b.maxRetryCount = v
  349. return b
  350. }
  351. func (b *Builder) TOTP(enable bool, config ...TOTPConfig) (r *Builder) {
  352. b.totpEnabled = enable
  353. if len(config) > 0 {
  354. b.totpConfig = config[0]
  355. }
  356. if enable {
  357. if b.totpConfig.Issuer == "" {
  358. panic("Issuer is empty")
  359. }
  360. }
  361. return b
  362. }
  363. func (b *Builder) NoForgetPasswordLink(v bool) (r *Builder) {
  364. b.noForgetPasswordLink = v
  365. return b
  366. }
  367. func (b *Builder) DB(v *gorm.DB) (r *Builder) {
  368. b.db = v
  369. return b
  370. }
  371. func (b *Builder) I18n(v *i18n.Builder) (r *Builder) {
  372. v.RegisterForModule(language.English, I18nLoginKey, Messages_en_US).
  373. RegisterForModule(language.SimplifiedChinese, I18nLoginKey, Messages_zh_CN).
  374. RegisterForModule(language.Japanese, I18nLoginKey, Messages_ja_JP)
  375. b.i18nBuilder = v
  376. return b
  377. }
  378. func (b *Builder) GetSessionMaxAge() int {
  379. return b.sessionMaxAge
  380. }
  381. func (b *Builder) ViewHelper() *ViewHelper {
  382. return &ViewHelper{
  383. b: b,
  384. }
  385. }
  386. func (b *Builder) UserModel(m interface{}) (r *Builder) {
  387. b.userModel = m
  388. b.tUser = underlyingReflectType(reflect.TypeOf(m))
  389. b.snakePrimaryField = snakePrimaryField(m)
  390. if _, ok := m.(UserPasser); ok {
  391. b.userPassEnabled = true
  392. }
  393. if _, ok := m.(OAuthUser); ok {
  394. b.oauthEnabled = true
  395. }
  396. if _, ok := m.(SessionSecurer); ok {
  397. b.sessionSecureEnabled = true
  398. }
  399. return b
  400. }
  401. func (b *Builder) newUserObject() interface{} {
  402. return reflect.New(b.tUser).Interface()
  403. }
  404. func (b *Builder) findUserByID(id string) (user interface{}, err error) {
  405. m := b.newUserObject()
  406. err = b.db.Where(fmt.Sprintf("%s = ?", b.snakePrimaryField), id).
  407. First(m).
  408. Error
  409. if err != nil {
  410. if err == gorm.ErrRecordNotFound {
  411. return nil, ErrUserNotFound
  412. }
  413. return nil, err
  414. }
  415. return m, nil
  416. }
  417. // completeUserAuthCallback is for url "/auth/{provider}/callback"
  418. func (b *Builder) completeUserAuthCallback(w http.ResponseWriter, r *http.Request) {
  419. if b.cookieConfig.SameSite != http.SameSiteStrictMode {
  420. b.completeUserAuthCallbackComplete(w, r)
  421. return
  422. }
  423. completeURL := fmt.Sprintf("%s?%s", b.oauthCallbackCompleteURL, r.URL.Query().Encode())
  424. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  425. w.Write([]byte(fmt.Sprintf(`
  426. <script>
  427. window.location.href="%s";
  428. </script>
  429. <a href="%s">complete</a>
  430. `, completeURL, completeURL)))
  431. return
  432. }
  433. func (b *Builder) completeUserAuthCallbackComplete(w http.ResponseWriter, r *http.Request) {
  434. var err error
  435. var user interface{}
  436. failRedirectURL := b.LogoutURL
  437. defer func() {
  438. if perr := recover(); perr != nil {
  439. panic(perr)
  440. }
  441. if err != nil {
  442. if b.afterFailedToLoginHook != nil {
  443. if herr := b.afterFailedToLoginHook(r, user, err); herr != nil {
  444. setNoticeOrPanic(w, herr)
  445. }
  446. }
  447. http.Redirect(w, r, failRedirectURL, http.StatusFound)
  448. }
  449. }()
  450. var ouser goth.User
  451. ouser, err = gothic.CompleteUserAuth(w, r)
  452. if err != nil {
  453. setFailCodeFlash(w, FailCodeCompleteUserAuthFailed)
  454. return
  455. }
  456. if b.afterOAuthCompleteHook != nil {
  457. if err = b.afterOAuthCompleteHook(r, ouser); err != nil {
  458. setNoticeOrPanic(w, err)
  459. return
  460. }
  461. }
  462. userID := ouser.UserID
  463. if b.userModel != nil {
  464. user, err = b.userModel.(OAuthUser).FindUserByOAuthUserID(b.db, b.newUserObject(), ouser.Provider, ouser.UserID)
  465. if err != nil {
  466. if err != gorm.ErrRecordNotFound {
  467. panic(err)
  468. }
  469. // TODO: maybe the identifier of some providers is not email
  470. identifier := ouser.Email
  471. user, err = b.userModel.(OAuthUser).FindUserByOAuthIdentifier(b.db, b.newUserObject(), ouser.Provider, identifier)
  472. if err != nil {
  473. if err != gorm.ErrRecordNotFound {
  474. panic(err)
  475. }
  476. setFailCodeFlash(w, FailCodeUserNotFound)
  477. return
  478. }
  479. err = user.(OAuthUser).InitOAuthUserID(b.db, b.newUserObject(), ouser.Provider, identifier, ouser.UserID)
  480. if err != nil {
  481. panic(err)
  482. }
  483. }
  484. userID = objectID(user)
  485. }
  486. claims := UserClaims{
  487. Provider: ouser.Provider,
  488. Email: ouser.Email,
  489. Name: ouser.Name,
  490. UserID: userID,
  491. AvatarURL: ouser.AvatarURL,
  492. RegisteredClaims: b.genBaseSessionClaim(userID),
  493. }
  494. if user == nil {
  495. user = &claims
  496. }
  497. if b.afterLoginHook != nil {
  498. setCookieForRequest(r, &http.Cookie{Name: b.authCookieName, Value: b.mustGetSessionToken(claims)})
  499. if err = b.afterLoginHook(r, user); err != nil {
  500. setNoticeOrPanic(w, err)
  501. return
  502. }
  503. }
  504. if err = b.setSecureCookiesByClaims(w, user, claims); err != nil {
  505. panic(err)
  506. }
  507. redirectURL := b.homePageURLFunc(r, user)
  508. if v := b.getContinueURL(w, r); v != "" {
  509. redirectURL = v
  510. }
  511. http.Redirect(w, r, redirectURL, http.StatusFound)
  512. return
  513. }
  514. // return user if account exists even if there is an error returned
  515. func (b *Builder) authUserPass(account string, password string) (user interface{}, err error) {
  516. user, err = b.userModel.(UserPasser).FindUser(b.db, b.newUserObject(), account)
  517. if err != nil {
  518. if err == gorm.ErrRecordNotFound {
  519. return nil, ErrUserNotFound
  520. }
  521. return nil, err
  522. }
  523. u := user.(UserPasser)
  524. if u.GetLocked() {
  525. return user, ErrUserLocked
  526. }
  527. if !u.IsPasswordCorrect(password) {
  528. if b.maxRetryCount > 0 {
  529. if err = u.IncreaseRetryCount(b.db, b.newUserObject()); err != nil {
  530. return user, err
  531. }
  532. if u.GetLoginRetryCount() >= b.maxRetryCount {
  533. if err = u.LockUser(b.db, b.newUserObject()); err != nil {
  534. return user, err
  535. }
  536. return user, ErrUserGetLocked
  537. }
  538. }
  539. return user, ErrWrongPassword
  540. }
  541. if u.GetLoginRetryCount() != 0 {
  542. if err = u.UnlockUser(b.db, b.newUserObject()); err != nil {
  543. return user, err
  544. }
  545. }
  546. return user, nil
  547. }
  548. func (b *Builder) userpassLogin(w http.ResponseWriter, r *http.Request) {
  549. if r.Method != http.MethodPost {
  550. w.WriteHeader(http.StatusNotFound)
  551. return
  552. }
  553. // check reCAPTCHA token
  554. if b.recaptchaEnabled {
  555. token := r.FormValue("token")
  556. if !recaptchaTokenCheck(b, token) {
  557. setFailCodeFlash(w, FailCodeIncorrectRecaptchaToken)
  558. http.Redirect(w, r, b.loginPageURL, http.StatusFound)
  559. return
  560. }
  561. }
  562. var err error
  563. var user interface{}
  564. failRedirectURL := b.LogoutURL
  565. defer func() {
  566. if perr := recover(); perr != nil {
  567. panic(perr)
  568. }
  569. if err != nil {
  570. if b.afterFailedToLoginHook != nil {
  571. if herr := b.afterFailedToLoginHook(r, user, err); herr != nil {
  572. setNoticeOrPanic(w, herr)
  573. }
  574. }
  575. http.Redirect(w, r, failRedirectURL, http.StatusFound)
  576. }
  577. }()
  578. account := r.FormValue("account")
  579. password := r.FormValue("password")
  580. user, err = b.authUserPass(account, password)
  581. if err != nil {
  582. if err == ErrUserGetLocked && b.afterUserLockedHook != nil {
  583. if err = b.afterUserLockedHook(r, user); err != nil {
  584. setNoticeOrPanic(w, err)
  585. return
  586. }
  587. }
  588. var code FailCode
  589. switch err {
  590. case ErrWrongPassword, ErrUserNotFound:
  591. code = FailCodeIncorrectAccountNameOrPassword
  592. case ErrUserLocked, ErrUserGetLocked:
  593. code = FailCodeUserLocked
  594. default:
  595. panic(err)
  596. }
  597. setFailCodeFlash(w, code)
  598. setWrongLoginInputFlash(w, WrongLoginInputFlash{
  599. Account: account,
  600. Password: password,
  601. })
  602. return
  603. }
  604. u := user.(UserPasser)
  605. userID := objectID(user)
  606. claims := UserClaims{
  607. UserID: userID,
  608. PassUpdatedAt: u.GetPasswordUpdatedAt(),
  609. RegisteredClaims: b.genBaseSessionClaim(userID),
  610. }
  611. if !b.totpEnabled {
  612. if b.afterLoginHook != nil {
  613. setCookieForRequest(r, &http.Cookie{Name: b.authCookieName, Value: b.mustGetSessionToken(claims)})
  614. if err = b.afterLoginHook(r, user); err != nil {
  615. setNoticeOrPanic(w, err)
  616. return
  617. }
  618. }
  619. }
  620. if err = b.setSecureCookiesByClaims(w, user, claims); err != nil {
  621. panic(err)
  622. }
  623. if b.totpEnabled {
  624. if u.GetIsTOTPSetup() {
  625. http.Redirect(w, r, b.totpValidatePageURL, http.StatusFound)
  626. return
  627. }
  628. var key *otp.Key
  629. if key, err = totp.Generate(
  630. totp.GenerateOpts{
  631. Issuer: b.totpConfig.Issuer,
  632. AccountName: u.GetAccountName(),
  633. },
  634. ); err != nil {
  635. panic(err)
  636. }
  637. if err = u.SetTOTPSecret(b.db, b.newUserObject(), key.Secret()); err != nil {
  638. panic(err)
  639. }
  640. http.Redirect(w, r, b.totpSetupPageURL, http.StatusFound)
  641. return
  642. }
  643. redirectURL := b.homePageURLFunc(r, user)
  644. if v := b.getContinueURL(w, r); v != "" {
  645. redirectURL = v
  646. }
  647. http.Redirect(w, r, redirectURL, http.StatusFound)
  648. return
  649. }
  650. func (b *Builder) genBaseSessionClaim(id string) jwt.RegisteredClaims {
  651. return genBaseClaims(id, b.sessionMaxAge)
  652. }
  653. func (b *Builder) mustGetSessionToken(claims UserClaims) string {
  654. return mustSignClaims(claims, b.secret)
  655. }
  656. func (b *Builder) setAuthCookiesFromUserClaims(w http.ResponseWriter, claims *UserClaims, secureSalt string) {
  657. http.SetCookie(w, &http.Cookie{
  658. Name: b.authCookieName,
  659. Value: b.mustGetSessionToken(*claims),
  660. Path: b.cookieConfig.Path,
  661. Domain: b.cookieConfig.Domain,
  662. MaxAge: b.sessionMaxAge,
  663. Expires: time.Now().Add(time.Duration(b.sessionMaxAge) * time.Second),
  664. HttpOnly: true,
  665. Secure: true,
  666. SameSite: b.cookieConfig.SameSite,
  667. })
  668. if secureSalt != "" {
  669. http.SetCookie(w, &http.Cookie{
  670. Name: b.authSecureCookieName,
  671. Value: mustSignClaims(&claims.RegisteredClaims, b.secret+secureSalt),
  672. Path: b.cookieConfig.Path,
  673. Domain: b.cookieConfig.Domain,
  674. MaxAge: b.sessionMaxAge,
  675. Expires: time.Now().Add(time.Duration(b.sessionMaxAge) * time.Second),
  676. HttpOnly: true,
  677. Secure: true,
  678. SameSite: b.cookieConfig.SameSite,
  679. })
  680. }
  681. }
  682. func (b *Builder) cleanAuthCookies(w http.ResponseWriter) {
  683. http.SetCookie(w, &http.Cookie{
  684. Name: b.authCookieName,
  685. Value: "",
  686. Path: b.cookieConfig.Path,
  687. Domain: b.cookieConfig.Domain,
  688. MaxAge: -1,
  689. Expires: time.Unix(1, 0),
  690. HttpOnly: true,
  691. Secure: true,
  692. })
  693. http.SetCookie(w, &http.Cookie{
  694. Name: b.authSecureCookieName,
  695. Value: "",
  696. Path: b.cookieConfig.Path,
  697. Domain: b.cookieConfig.Domain,
  698. MaxAge: -1,
  699. Expires: time.Unix(1, 0),
  700. HttpOnly: true,
  701. Secure: true,
  702. })
  703. }
  704. func (b *Builder) setContinueURL(w http.ResponseWriter, r *http.Request) {
  705. continueURL := r.RequestURI
  706. if strings.Contains(continueURL, "?__execute_event__=") {
  707. continueURL = r.Referer()
  708. }
  709. ignore := false
  710. {
  711. ignoreURLs := map[string]struct{}{
  712. b.loginPageURL: {},
  713. b.resetPasswordPageURL: {},
  714. b.forgetPasswordPageURL: {},
  715. b.resetPasswordLinkSentPageURL: {},
  716. b.totpSetupPageURL: {},
  717. b.totpValidatePageURL: {},
  718. b.LogoutURL: {},
  719. }
  720. u, err := url.Parse(continueURL)
  721. if err != nil {
  722. ignore = true
  723. } else {
  724. if _, ok := ignoreURLs[u.Path]; ok {
  725. ignore = true
  726. }
  727. }
  728. }
  729. if ignore {
  730. return
  731. }
  732. http.SetCookie(w, &http.Cookie{
  733. Name: b.continueUrlCookieName,
  734. Value: continueURL,
  735. Path: b.cookieConfig.Path,
  736. Domain: b.cookieConfig.Domain,
  737. HttpOnly: true,
  738. })
  739. }
  740. func (b *Builder) getContinueURL(w http.ResponseWriter, r *http.Request) string {
  741. c, err := r.Cookie(b.continueUrlCookieName)
  742. if err != nil || c.Value == "" {
  743. return ""
  744. }
  745. http.SetCookie(w, &http.Cookie{
  746. Name: b.continueUrlCookieName,
  747. Value: "",
  748. MaxAge: -1,
  749. Expires: time.Unix(1, 0),
  750. Path: b.cookieConfig.Path,
  751. Domain: b.cookieConfig.Domain,
  752. HttpOnly: true,
  753. })
  754. return c.Value
  755. }
  756. func (b *Builder) setSecureCookiesByClaims(w http.ResponseWriter, user interface{}, claims UserClaims) (err error) {
  757. var secureSalt string
  758. if b.sessionSecureEnabled {
  759. if user.(SessionSecurer).GetSecure() == "" {
  760. err = user.(SessionSecurer).UpdateSecure(b.db, b.newUserObject(), objectID(user))
  761. if err != nil {
  762. return err
  763. }
  764. }
  765. secureSalt = user.(SessionSecurer).GetSecure()
  766. }
  767. b.setAuthCookiesFromUserClaims(w, &claims, secureSalt)
  768. return nil
  769. }
  770. func (b *Builder) consumeTOTPCode(r *http.Request, up UserPasser, passcode string) error {
  771. if !totp.Validate(passcode, up.GetTOTPSecret()) {
  772. return ErrWrongTOTPCode
  773. }
  774. lastCode, usedAt := up.GetLastUsedTOTPCode()
  775. if usedAt != nil && time.Now().Sub(*usedAt) > 90*time.Second {
  776. lastCode = ""
  777. }
  778. if passcode == lastCode {
  779. if b.afterTOTPCodeReusedHook != nil {
  780. if herr := b.afterTOTPCodeReusedHook(r, GetCurrentUser(r)); herr != nil {
  781. return herr
  782. }
  783. }
  784. return ErrTOTPCodeHasBeenUsed
  785. }
  786. if err := up.SetLastUsedTOTPCode(b.db, b.newUserObject(), passcode); err != nil {
  787. return err
  788. }
  789. return nil
  790. }
  791. // logout is for url "/logout/{provider}"
  792. func (b *Builder) logout(w http.ResponseWriter, r *http.Request) {
  793. err := gothic.Logout(w, r)
  794. if err != nil {
  795. //
  796. }
  797. b.cleanAuthCookies(w)
  798. if b.afterLogoutHook != nil {
  799. user := GetCurrentUser(r)
  800. if user != nil {
  801. if herr := b.afterLogoutHook(r, user); herr != nil {
  802. setNoticeOrPanic(w, herr)
  803. http.Redirect(w, r, b.loginPageURL, http.StatusFound)
  804. return
  805. }
  806. }
  807. }
  808. http.Redirect(w, r, b.loginPageURL, http.StatusFound)
  809. }
  810. // beginAuth is for url "/auth/{provider}"
  811. func (b *Builder) beginAuth(w http.ResponseWriter, r *http.Request) {
  812. gothic.BeginAuthHandler(w, r)
  813. }
  814. func (b *Builder) sendResetPasswordLink(w http.ResponseWriter, r *http.Request) {
  815. if r.Method != http.MethodPost {
  816. w.WriteHeader(http.StatusNotFound)
  817. return
  818. }
  819. failRedirectURL := b.forgetPasswordPageURL
  820. // check reCAPTCHA token
  821. if b.recaptchaEnabled {
  822. token := r.FormValue("token")
  823. if !recaptchaTokenCheck(b, token) {
  824. setFailCodeFlash(w, FailCodeIncorrectRecaptchaToken)
  825. http.Redirect(w, r, failRedirectURL, http.StatusFound)
  826. return
  827. }
  828. }
  829. account := strings.TrimSpace(r.FormValue("account"))
  830. passcode := r.FormValue("otp")
  831. doTOTP := r.URL.Query().Get("totp") == "1"
  832. if doTOTP {
  833. failRedirectURL = MustSetQuery(failRedirectURL, "totp", "1")
  834. }
  835. if account == "" {
  836. setFailCodeFlash(w, FailCodeAccountIsRequired)
  837. http.Redirect(w, r, failRedirectURL, http.StatusFound)
  838. return
  839. }
  840. u, err := b.userModel.(UserPasser).FindUser(b.db, b.newUserObject(), account)
  841. if err != nil {
  842. if errors.Is(err, gorm.ErrRecordNotFound) {
  843. http.Redirect(w, r, fmt.Sprintf("%s?a=%s", b.resetPasswordLinkSentPageURL, account), http.StatusFound)
  844. return
  845. }
  846. panic(err)
  847. }
  848. _, createdAt, _ := u.(UserPasser).GetResetPasswordToken()
  849. if createdAt != nil {
  850. v := 60 - int(time.Now().Sub(*createdAt).Seconds())
  851. if v > 0 {
  852. setSecondsToRedoFlash(w, v)
  853. setWrongForgetPasswordInputFlash(w, WrongForgetPasswordInputFlash{
  854. Account: account,
  855. })
  856. http.Redirect(w, r, failRedirectURL, http.StatusFound)
  857. return
  858. }
  859. }
  860. if u.(UserPasser).GetIsTOTPSetup() {
  861. if !doTOTP {
  862. setWrongForgetPasswordInputFlash(w, WrongForgetPasswordInputFlash{
  863. Account: account,
  864. })
  865. failRedirectURL = MustSetQuery(failRedirectURL, "totp", "1")
  866. http.Redirect(w, r, failRedirectURL, http.StatusFound)
  867. return
  868. }
  869. if err = b.consumeTOTPCode(r, u.(UserPasser), passcode); err != nil {
  870. var fc FailCode
  871. switch err {
  872. case ErrWrongTOTPCode:
  873. fc = FailCodeIncorrectTOTPCode
  874. case ErrTOTPCodeHasBeenUsed:
  875. fc = FailCodeTOTPCodeHasBeenUsed
  876. default:
  877. panic(err)
  878. }
  879. setNoticeOrFailCodeFlash(w, err, fc)
  880. setWrongForgetPasswordInputFlash(w, WrongForgetPasswordInputFlash{
  881. Account: account,
  882. TOTP: passcode,
  883. })
  884. http.Redirect(w, r, failRedirectURL, http.StatusFound)
  885. return
  886. }
  887. }
  888. token, err := u.(UserPasser).GenerateResetPasswordToken(b.db, b.newUserObject())
  889. if err != nil {
  890. panic(err)
  891. }
  892. scheme := "https"
  893. if r.TLS == nil {
  894. scheme = "http"
  895. }
  896. link := fmt.Sprintf("%s://%s%s?id=%s&token=%s", scheme, r.Host, b.resetPasswordPageURL, objectID(u), token)
  897. if doTOTP {
  898. link = MustSetQuery(link, "totp", "1")
  899. }
  900. if b.afterConfirmSendResetPasswordLinkHook != nil {
  901. if herr := b.afterConfirmSendResetPasswordLinkHook(r, u, link); herr != nil {
  902. setNoticeOrPanic(w, herr)
  903. http.Redirect(w, r, failRedirectURL, http.StatusFound)
  904. return
  905. }
  906. }
  907. http.Redirect(w, r, fmt.Sprintf("%s?a=%s", b.resetPasswordLinkSentPageURL, account), http.StatusFound)
  908. return
  909. }
  910. func (b *Builder) doResetPassword(w http.ResponseWriter, r *http.Request) {
  911. if r.Method != http.MethodPost {
  912. w.WriteHeader(http.StatusNotFound)
  913. return
  914. }
  915. userID := r.FormValue("user_id")
  916. token := r.FormValue("token")
  917. passcode := r.FormValue("otp")
  918. doTOTP := r.URL.Query().Get("totp") == "1"
  919. failRedirectURL := fmt.Sprintf("%s?id=%s&token=%s", b.resetPasswordPageURL, userID, token)
  920. if doTOTP {
  921. failRedirectURL = MustSetQuery(failRedirectURL, "totp", "1")
  922. }
  923. if userID == "" {
  924. setFailCodeFlash(w, FailCodeUserNotFound)
  925. http.Redirect(w, r, failRedirectURL, http.StatusFound)
  926. return
  927. }
  928. if token == "" {
  929. setFailCodeFlash(w, FailCodeInvalidToken)
  930. http.Redirect(w, r, failRedirectURL, http.StatusFound)
  931. return
  932. }
  933. password := r.FormValue("password")
  934. confirmPassword := r.FormValue("confirm_password")
  935. if password == "" {
  936. setFailCodeFlash(w, FailCodePasswordCannotBeEmpty)
  937. http.Redirect(w, r, failRedirectURL, http.StatusFound)
  938. return
  939. }
  940. if confirmPassword != password {
  941. setFailCodeFlash(w, FailCodePasswordNotMatch)
  942. setWrongResetPasswordInputFlash(w, WrongResetPasswordInputFlash{
  943. Password: password,
  944. ConfirmPassword: confirmPassword,
  945. })
  946. http.Redirect(w, r, failRedirectURL, http.StatusFound)
  947. return
  948. }
  949. u, err := b.findUserByID(userID)
  950. if err != nil {
  951. if err == ErrUserNotFound {
  952. setFailCodeFlash(w, FailCodeUserNotFound)
  953. http.Redirect(w, r, failRedirectURL, http.StatusFound)
  954. return
  955. }
  956. panic(err)
  957. }
  958. storedToken, _, expired := u.(UserPasser).GetResetPasswordToken()
  959. if expired {
  960. setFailCodeFlash(w, FailCodeTokenExpired)
  961. http.Redirect(w, r, failRedirectURL, http.StatusFound)
  962. return
  963. }
  964. if token != storedToken {
  965. setFailCodeFlash(w, FailCodeInvalidToken)
  966. http.Redirect(w, r, failRedirectURL, http.StatusFound)
  967. return
  968. }
  969. if b.beforeSetPasswordHook != nil {
  970. if herr := b.beforeSetPasswordHook(r, u, password); herr != nil {
  971. setNoticeOrPanic(w, herr)
  972. setWrongResetPasswordInputFlash(w, WrongResetPasswordInputFlash{
  973. Password: password,
  974. ConfirmPassword: confirmPassword,
  975. })
  976. http.Redirect(w, r, failRedirectURL, http.StatusFound)
  977. return
  978. }
  979. }
  980. if u.(UserPasser).GetIsTOTPSetup() {
  981. if !doTOTP {
  982. setWrongResetPasswordInputFlash(w, WrongResetPasswordInputFlash{
  983. Password: password,
  984. ConfirmPassword: confirmPassword,
  985. })
  986. failRedirectURL = MustSetQuery(failRedirectURL, "totp", "1")
  987. http.Redirect(w, r, failRedirectURL, http.StatusFound)
  988. return
  989. }
  990. if err = b.consumeTOTPCode(r, u.(UserPasser), passcode); err != nil {
  991. var fc FailCode
  992. switch err {
  993. case ErrWrongTOTPCode:
  994. fc = FailCodeIncorrectTOTPCode
  995. case ErrTOTPCodeHasBeenUsed:
  996. fc = FailCodeTOTPCodeHasBeenUsed
  997. default:
  998. panic(err)
  999. }
  1000. setFailCodeFlash(w, fc)
  1001. setWrongResetPasswordInputFlash(w, WrongResetPasswordInputFlash{
  1002. Password: password,
  1003. ConfirmPassword: confirmPassword,
  1004. TOTP: passcode,
  1005. })
  1006. http.Redirect(w, r, failRedirectURL, http.StatusFound)
  1007. return
  1008. }
  1009. }
  1010. err = u.(UserPasser).ConsumeResetPasswordToken(b.db, b.newUserObject())
  1011. if err != nil {
  1012. panic(err)
  1013. }
  1014. err = u.(UserPasser).SetPassword(b.db, b.newUserObject(), password)
  1015. if err != nil {
  1016. panic(err)
  1017. }
  1018. if b.afterResetPasswordHook != nil {
  1019. if herr := b.afterResetPasswordHook(r, u); herr != nil {
  1020. setNoticeOrPanic(w, herr)
  1021. http.Redirect(w, r, failRedirectURL, http.StatusFound)
  1022. return
  1023. }
  1024. }
  1025. setInfoCodeFlash(w, InfoCodePasswordSuccessfullyReset)
  1026. http.Redirect(w, r, b.loginPageURL, http.StatusFound)
  1027. return
  1028. }
  1029. // NoticeError
  1030. // ErrWrongPassword
  1031. // ErrEmptyPassword
  1032. // ErrPasswordNotMatch
  1033. // ErrWrongTOTPCode
  1034. // ErrTOTPCodeHasBeenUsed
  1035. func (b *Builder) ChangePassword(
  1036. r *http.Request,
  1037. oldPassword string,
  1038. password string,
  1039. confirmPassword string,
  1040. otp string,
  1041. ) error {
  1042. user := GetCurrentUser(r).(UserPasser)
  1043. if !user.IsPasswordCorrect(oldPassword) {
  1044. return ErrWrongPassword
  1045. }
  1046. if password == "" {
  1047. return ErrEmptyPassword
  1048. }
  1049. if confirmPassword != password {
  1050. return ErrPasswordNotMatch
  1051. }
  1052. if b.beforeSetPasswordHook != nil {
  1053. if herr := b.beforeSetPasswordHook(r, user, password); herr != nil {
  1054. return herr
  1055. }
  1056. }
  1057. if b.totpEnabled {
  1058. if err := b.consumeTOTPCode(r, user, otp); err != nil {
  1059. return err
  1060. }
  1061. }
  1062. err := user.SetPassword(b.db, b.newUserObject(), password)
  1063. if err != nil {
  1064. return err
  1065. }
  1066. if b.afterChangePasswordHook != nil {
  1067. if herr := b.afterChangePasswordHook(r, user); herr != nil {
  1068. return herr
  1069. }
  1070. }
  1071. return nil
  1072. }
  1073. func (b *Builder) doFormChangePassword(w http.ResponseWriter, r *http.Request) {
  1074. if r.Method != http.MethodPost {
  1075. w.WriteHeader(http.StatusNotFound)
  1076. return
  1077. }
  1078. oldPassword := r.FormValue("old_password")
  1079. password := r.FormValue("password")
  1080. confirmPassword := r.FormValue("confirm_password")
  1081. otp := r.FormValue("otp")
  1082. redirectURL := b.changePasswordPageURL
  1083. err := b.ChangePassword(r, oldPassword, password, confirmPassword, otp)
  1084. if err != nil {
  1085. if ne, ok := err.(*NoticeError); ok {
  1086. setNoticeFlash(w, ne)
  1087. } else {
  1088. var fc FailCode
  1089. switch err {
  1090. case ErrWrongPassword:
  1091. fc = FailCodeIncorrectPassword
  1092. case ErrEmptyPassword:
  1093. fc = FailCodePasswordCannotBeEmpty
  1094. case ErrPasswordNotMatch:
  1095. fc = FailCodePasswordNotMatch
  1096. case ErrWrongTOTPCode:
  1097. fc = FailCodeIncorrectTOTPCode
  1098. case ErrTOTPCodeHasBeenUsed:
  1099. fc = FailCodeTOTPCodeHasBeenUsed
  1100. default:
  1101. panic(err)
  1102. }
  1103. setFailCodeFlash(w, fc)
  1104. }
  1105. setWrongChangePasswordInputFlash(w, WrongChangePasswordInputFlash{
  1106. OldPassword: oldPassword,
  1107. NewPassword: password,
  1108. ConfirmPassword: confirmPassword,
  1109. TOTP: otp,
  1110. })
  1111. http.Redirect(w, r, redirectURL, http.StatusFound)
  1112. return
  1113. }
  1114. setInfoCodeFlash(w, InfoCodePasswordSuccessfullyChanged)
  1115. http.Redirect(w, r, b.loginPageURL, http.StatusFound)
  1116. }
  1117. func (b *Builder) totpDo(w http.ResponseWriter, r *http.Request) {
  1118. if r.Method != http.MethodPost {
  1119. w.WriteHeader(http.StatusNotFound)
  1120. return
  1121. }
  1122. var claims *UserClaims
  1123. claims, err := parseUserClaimsFromCookie(r, b.authCookieName, b.secret)
  1124. if err != nil {
  1125. http.Redirect(w, r, b.LogoutURL, http.StatusFound)
  1126. return
  1127. }
  1128. user, err := b.findUserByID(claims.UserID)
  1129. if err != nil {
  1130. if err == ErrUserNotFound {
  1131. setFailCodeFlash(w, FailCodeUserNotFound)
  1132. http.Redirect(w, r, b.LogoutURL, http.StatusFound)
  1133. return
  1134. }
  1135. panic(err)
  1136. }
  1137. failRedirectURL := b.LogoutURL
  1138. defer func() {
  1139. if perr := recover(); perr != nil {
  1140. panic(perr)
  1141. }
  1142. if err != nil {
  1143. if b.afterFailedToLoginHook != nil {
  1144. if herr := b.afterFailedToLoginHook(r, user, err); herr != nil {
  1145. setNoticeOrPanic(w, herr)
  1146. }
  1147. }
  1148. http.Redirect(w, r, failRedirectURL, http.StatusFound)
  1149. }
  1150. }()
  1151. u := user.(UserPasser)
  1152. otp := r.FormValue("otp")
  1153. isTOTPSetup := u.GetIsTOTPSetup()
  1154. if err = b.consumeTOTPCode(r, u, otp); err != nil {
  1155. var fc FailCode
  1156. switch err {
  1157. case ErrWrongTOTPCode:
  1158. fc = FailCodeIncorrectTOTPCode
  1159. case ErrTOTPCodeHasBeenUsed:
  1160. fc = FailCodeTOTPCodeHasBeenUsed
  1161. default:
  1162. panic(err)
  1163. }
  1164. setFailCodeFlash(w, fc)
  1165. failRedirectURL = b.totpValidatePageURL
  1166. if !isTOTPSetup {
  1167. failRedirectURL = b.totpSetupPageURL
  1168. }
  1169. return
  1170. }
  1171. if !isTOTPSetup {
  1172. if err = u.SetIsTOTPSetup(b.db, b.newUserObject(), true); err != nil {
  1173. panic(err)
  1174. }
  1175. }
  1176. claims.TOTPValidated = true
  1177. if b.afterLoginHook != nil {
  1178. setCookieForRequest(r, &http.Cookie{Name: b.authCookieName, Value: b.mustGetSessionToken(*claims)})
  1179. if err = b.afterLoginHook(r, user); err != nil {
  1180. setNoticeOrPanic(w, err)
  1181. return
  1182. }
  1183. }
  1184. err = b.setSecureCookiesByClaims(w, user, *claims)
  1185. if err != nil {
  1186. panic(err)
  1187. }
  1188. redirectURL := b.homePageURLFunc(r, user)
  1189. if v := b.getContinueURL(w, r); v != "" {
  1190. redirectURL = v
  1191. }
  1192. http.Redirect(w, r, redirectURL, http.StatusFound)
  1193. }
  1194. func (b *Builder) Mount(mux *http.ServeMux) {
  1195. b.MountAPI(mux)
  1196. // pages
  1197. wb := web.New()
  1198. mux.Handle(b.loginPageURL, b.i18nBuilder.EnsureLanguage(wb.Page(b.loginPageFunc)))
  1199. if b.userPassEnabled {
  1200. mux.Handle(b.resetPasswordPageURL, b.i18nBuilder.EnsureLanguage(wb.Page(b.resetPasswordPageFunc)))
  1201. mux.Handle(b.changePasswordPageURL, b.i18nBuilder.EnsureLanguage(wb.Page(b.changePasswordPageFunc)))
  1202. if !b.noForgetPasswordLink {
  1203. mux.Handle(b.forgetPasswordPageURL, b.i18nBuilder.EnsureLanguage(wb.Page(b.forgetPasswordPageFunc)))
  1204. mux.Handle(b.resetPasswordLinkSentPageURL, b.i18nBuilder.EnsureLanguage(wb.Page(b.resetPasswordLinkSentPageFunc)))
  1205. }
  1206. if b.totpEnabled {
  1207. mux.Handle(b.totpSetupPageURL, b.i18nBuilder.EnsureLanguage(wb.Page(b.totpSetupPageFunc)))
  1208. mux.Handle(b.totpValidatePageURL, b.i18nBuilder.EnsureLanguage(wb.Page(b.totpValidatePageFunc)))
  1209. }
  1210. }
  1211. // assets
  1212. assetsSubFS, err := fs.Sub(assetsFS, "assets")
  1213. if err != nil {
  1214. panic(err)
  1215. }
  1216. mux.Handle(assetsPathPrefix, http.StripPrefix(assetsPathPrefix, http.FileServer(http.FS(assetsSubFS))))
  1217. }
  1218. func (b *Builder) MountAPI(mux *http.ServeMux) {
  1219. if len(b.secret) == 0 {
  1220. panic("secret is empty")
  1221. }
  1222. if b.userModel != nil {
  1223. if b.db == nil {
  1224. panic("db is required")
  1225. }
  1226. }
  1227. mux.HandleFunc(b.LogoutURL, b.logout)
  1228. if b.userPassEnabled {
  1229. mux.HandleFunc(b.passwordLoginURL, b.userpassLogin)
  1230. mux.HandleFunc(b.resetPasswordURL, b.doResetPassword)
  1231. mux.HandleFunc(b.changePasswordURL, b.doFormChangePassword)
  1232. if !b.noForgetPasswordLink {
  1233. mux.HandleFunc(b.sendResetPasswordLinkURL, b.sendResetPasswordLink)
  1234. }
  1235. if b.totpEnabled {
  1236. mux.HandleFunc(b.validateTOTPURL, b.totpDo)
  1237. }
  1238. }
  1239. if b.oauthEnabled {
  1240. mux.HandleFunc(b.oauthBeginURL, b.beginAuth)
  1241. mux.HandleFunc(b.oauthCallbackURL, b.completeUserAuthCallback)
  1242. mux.HandleFunc(b.oauthCallbackCompleteURL, b.completeUserAuthCallbackComplete)
  1243. }
  1244. }