2019-04-25 04:52:08 +07:00
|
|
|
package middlewares
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2022-07-18 07:56:09 +07:00
|
|
|
"net/mail"
|
2022-01-21 06:46:13 +07:00
|
|
|
"time"
|
2019-04-25 04:52:08 +07:00
|
|
|
|
2021-08-04 04:38:07 +07:00
|
|
|
"github.com/golang-jwt/jwt/v4"
|
2021-12-04 11:48:22 +07:00
|
|
|
"github.com/google/uuid"
|
2020-04-05 19:37:21 +07:00
|
|
|
|
2022-03-06 12:47:40 +07:00
|
|
|
"github.com/authelia/authelia/v4/internal/model"
|
2021-08-11 08:04:35 +07:00
|
|
|
"github.com/authelia/authelia/v4/internal/templates"
|
2019-04-25 04:52:08 +07:00
|
|
|
)
|
|
|
|
|
|
|
|
// IdentityVerificationStart the handler for initiating the identity validation process.
|
2022-01-21 06:46:13 +07:00
|
|
|
func IdentityVerificationStart(args IdentityVerificationStartArgs, delayFunc TimingAttackDelayFunc) RequestHandler {
|
2019-04-25 04:52:08 +07:00
|
|
|
if args.IdentityRetrieverFunc == nil {
|
|
|
|
panic(fmt.Errorf("Identity verification requires an identity retriever"))
|
|
|
|
}
|
|
|
|
|
|
|
|
return func(ctx *AutheliaCtx) {
|
2022-01-21 06:46:13 +07:00
|
|
|
requestTime := time.Now()
|
|
|
|
success := false
|
|
|
|
|
|
|
|
if delayFunc != nil {
|
2022-06-14 14:20:13 +07:00
|
|
|
defer delayFunc(ctx, requestTime, &success)
|
2022-01-21 06:46:13 +07:00
|
|
|
}
|
|
|
|
|
2019-04-25 04:52:08 +07:00
|
|
|
identity, err := args.IdentityRetrieverFunc(ctx)
|
|
|
|
if err != nil {
|
|
|
|
// In that case we reply ok to avoid user enumeration.
|
|
|
|
ctx.Logger.Error(err)
|
|
|
|
ctx.ReplyOK()
|
2020-05-06 02:35:32 +07:00
|
|
|
|
2019-04-25 04:52:08 +07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-12-04 11:48:22 +07:00
|
|
|
var jti uuid.UUID
|
|
|
|
|
2022-03-16 07:29:46 +07:00
|
|
|
if jti, err = uuid.NewRandom(); err != nil {
|
2021-12-04 11:48:22 +07:00
|
|
|
ctx.Error(err, messageOperationFailed)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-03-06 12:47:40 +07:00
|
|
|
verification := model.NewIdentityVerification(jti, identity.Username, args.ActionClaim, ctx.RemoteIP())
|
2021-11-30 13:58:21 +07:00
|
|
|
|
2019-04-25 04:52:08 +07:00
|
|
|
// Create the claim with the action to sign it.
|
2021-11-30 13:58:21 +07:00
|
|
|
claims := verification.ToIdentityVerificationClaim()
|
|
|
|
|
2019-04-25 04:52:08 +07:00
|
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
|
|
|
2022-05-09 05:43:12 +07:00
|
|
|
ss, err := token.SignedString([]byte(ctx.Configuration.JWTSecret))
|
2019-04-25 04:52:08 +07:00
|
|
|
if err != nil {
|
2021-07-22 10:52:37 +07:00
|
|
|
ctx.Error(err, messageOperationFailed)
|
2019-04-25 04:52:08 +07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-05-09 05:43:12 +07:00
|
|
|
if err = ctx.Providers.StorageProvider.SaveIdentityVerification(ctx, verification); err != nil {
|
2021-07-22 10:52:37 +07:00
|
|
|
ctx.Error(err, messageOperationFailed)
|
2019-04-25 04:52:08 +07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-08-10 07:31:08 +07:00
|
|
|
uri, err := ctx.ExternalRootURL()
|
2021-05-05 05:06:05 +07:00
|
|
|
if err != nil {
|
2021-07-22 10:52:37 +07:00
|
|
|
ctx.Error(err, messageOperationFailed)
|
2020-02-13 09:12:37 +07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-08-10 07:31:08 +07:00
|
|
|
link := fmt.Sprintf("%s%s?token=%s", uri, args.TargetEndpoint, ss)
|
2019-04-25 04:52:08 +07:00
|
|
|
|
2020-08-21 09:16:23 +07:00
|
|
|
bufHTML := new(bytes.Buffer)
|
|
|
|
|
|
|
|
disableHTML := false
|
2022-04-16 06:34:26 +07:00
|
|
|
if ctx.Configuration.Notifier.SMTP != nil {
|
2020-08-21 09:16:23 +07:00
|
|
|
disableHTML = ctx.Configuration.Notifier.SMTP.DisableHTMLEmails
|
|
|
|
}
|
|
|
|
|
2022-07-18 07:56:09 +07:00
|
|
|
values := templates.EmailIdentityVerificationValues{
|
|
|
|
Title: args.MailTitle,
|
|
|
|
LinkURL: link,
|
|
|
|
LinkText: args.MailButtonContent,
|
|
|
|
DisplayName: identity.DisplayName,
|
|
|
|
RemoteIP: ctx.RemoteIP().String(),
|
2022-05-09 05:43:12 +07:00
|
|
|
}
|
2020-08-21 09:16:23 +07:00
|
|
|
|
2022-05-09 05:43:12 +07:00
|
|
|
if !disableHTML {
|
2022-07-18 07:56:09 +07:00
|
|
|
if err = ctx.Providers.Templates.ExecuteEmailIdentityVerificationTemplate(bufHTML, values, templates.HTMLFormat); err != nil {
|
2021-07-22 10:52:37 +07:00
|
|
|
ctx.Error(err, messageOperationFailed)
|
2020-08-21 09:16:23 +07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bufText := new(bytes.Buffer)
|
|
|
|
|
2022-07-18 07:56:09 +07:00
|
|
|
if err = ctx.Providers.Templates.ExecuteEmailIdentityVerificationTemplate(bufText, values, templates.PlainTextFormat); err != nil {
|
2021-07-22 10:52:37 +07:00
|
|
|
ctx.Error(err, messageOperationFailed)
|
2019-04-25 04:52:08 +07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-17 08:05:46 +07:00
|
|
|
ctx.Logger.Debugf("Sending an email to user %s (%s) to confirm identity for registering a device.",
|
2019-04-25 04:52:08 +07:00
|
|
|
identity.Username, identity.Email)
|
2020-05-06 02:35:32 +07:00
|
|
|
|
2022-07-18 07:56:09 +07:00
|
|
|
if err = ctx.Providers.Notifier.Send(mail.Address{Name: identity.DisplayName, Address: identity.Email}, args.MailTitle, bufText.String(), bufHTML.String()); err != nil {
|
2021-07-22 10:52:37 +07:00
|
|
|
ctx.Error(err, messageOperationFailed)
|
2019-04-25 04:52:08 +07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-21 06:46:13 +07:00
|
|
|
success = true
|
|
|
|
|
2019-04-25 04:52:08 +07:00
|
|
|
ctx.ReplyOK()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// IdentityVerificationFinish the middleware for finishing the identity validation process.
|
|
|
|
func IdentityVerificationFinish(args IdentityVerificationFinishArgs, next func(ctx *AutheliaCtx, username string)) RequestHandler {
|
|
|
|
return func(ctx *AutheliaCtx) {
|
|
|
|
var finishBody IdentityVerificationFinishBody
|
2020-05-06 02:35:32 +07:00
|
|
|
|
2019-04-25 04:52:08 +07:00
|
|
|
b := ctx.PostBody()
|
|
|
|
|
|
|
|
err := json.Unmarshal(b, &finishBody)
|
|
|
|
|
|
|
|
if err != nil {
|
2021-07-22 10:52:37 +07:00
|
|
|
ctx.Error(err, messageOperationFailed)
|
2019-04-25 04:52:08 +07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if finishBody.Token == "" {
|
2021-07-22 10:52:37 +07:00
|
|
|
ctx.Error(fmt.Errorf("No token provided"), messageOperationFailed)
|
2019-04-25 04:52:08 +07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-03-06 12:47:40 +07:00
|
|
|
token, err := jwt.ParseWithClaims(finishBody.Token, &model.IdentityVerificationClaim{},
|
2019-04-25 04:52:08 +07:00
|
|
|
func(token *jwt.Token) (interface{}, error) {
|
|
|
|
return []byte(ctx.Configuration.JWTSecret), nil
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
if ve, ok := err.(*jwt.ValidationError); ok {
|
2020-05-06 07:52:06 +07:00
|
|
|
switch {
|
|
|
|
case ve.Errors&jwt.ValidationErrorMalformed != 0:
|
2021-07-22 10:52:37 +07:00
|
|
|
ctx.Error(fmt.Errorf("Cannot parse token"), messageOperationFailed)
|
2019-04-25 04:52:08 +07:00
|
|
|
return
|
2020-05-06 07:52:06 +07:00
|
|
|
case ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0:
|
2022-01-31 12:25:15 +07:00
|
|
|
// Token is either expired or not active yet.
|
2021-07-22 10:52:37 +07:00
|
|
|
ctx.Error(fmt.Errorf("Token expired"), messageIdentityVerificationTokenHasExpired)
|
2019-04-25 04:52:08 +07:00
|
|
|
return
|
2020-05-06 07:52:06 +07:00
|
|
|
default:
|
2021-07-22 10:52:37 +07:00
|
|
|
ctx.Error(fmt.Errorf("Cannot handle this token: %s", ve), messageOperationFailed)
|
2019-04-25 04:52:08 +07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2020-05-06 02:35:32 +07:00
|
|
|
|
2021-07-22 10:52:37 +07:00
|
|
|
ctx.Error(err, messageOperationFailed)
|
2020-05-06 02:35:32 +07:00
|
|
|
|
2019-04-25 04:52:08 +07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-03-06 12:47:40 +07:00
|
|
|
claims, ok := token.Claims.(*model.IdentityVerificationClaim)
|
2019-04-25 04:52:08 +07:00
|
|
|
if !ok {
|
2021-07-22 10:52:37 +07:00
|
|
|
ctx.Error(fmt.Errorf("Wrong type of claims (%T != *middlewares.IdentityVerificationClaim)", claims), messageOperationFailed)
|
2019-04-25 04:52:08 +07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-11-30 13:58:21 +07:00
|
|
|
verification, err := claims.ToIdentityVerification()
|
|
|
|
if err != nil {
|
|
|
|
ctx.Error(fmt.Errorf("Token seems to be invalid: %w", err),
|
|
|
|
messageOperationFailed)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
found, err := ctx.Providers.StorageProvider.FindIdentityVerification(ctx, verification.JTI.String())
|
|
|
|
if err != nil {
|
|
|
|
ctx.Error(err, messageOperationFailed)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if !found {
|
|
|
|
ctx.Error(fmt.Errorf("Token is not in DB, it might have already been used"),
|
|
|
|
messageIdentityVerificationTokenAlreadyUsed)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-04-25 04:52:08 +07:00
|
|
|
// Verify that the action claim in the token is the one expected for the given endpoint.
|
|
|
|
if claims.Action != args.ActionClaim {
|
2021-07-22 10:52:37 +07:00
|
|
|
ctx.Error(fmt.Errorf("This token has not been generated for this kind of action"), messageOperationFailed)
|
2019-04-25 04:52:08 +07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if args.IsTokenUserValidFunc != nil && !args.IsTokenUserValidFunc(ctx, claims.Username) {
|
2021-07-22 10:52:37 +07:00
|
|
|
ctx.Error(fmt.Errorf("This token has not been generated for this user"), messageOperationFailed)
|
2019-04-25 04:52:08 +07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-03-06 12:47:40 +07:00
|
|
|
err = ctx.Providers.StorageProvider.ConsumeIdentityVerification(ctx, claims.ID, model.NewNullIP(ctx.RemoteIP()))
|
2019-04-25 04:52:08 +07:00
|
|
|
if err != nil {
|
2021-07-22 10:52:37 +07:00
|
|
|
ctx.Error(err, messageOperationFailed)
|
2019-04-25 04:52:08 +07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
next(ctx, claims.Username)
|
|
|
|
}
|
|
|
|
}
|