builder.go 35 KB

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