Skip to content

Commit

Permalink
Add 2fa resend
Browse files Browse the repository at this point in the history
  • Loading branch information
sokolovstas committed Nov 9, 2021
1 parent 8fc76ba commit eed433b
Show file tree
Hide file tree
Showing 30 changed files with 30,630 additions and 3,336 deletions.
5 changes: 3 additions & 2 deletions cmd/config-boltdb.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,10 @@ login:
loginWith:
username: false
phone: false
email: true
email: false
federated: false
tfaType: sms
tfaType: app
tfaResendTimeout: 0
keyStorage:
type: local
s3:
Expand Down
1 change: 1 addition & 0 deletions cmd/config-dynamodb.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ login: # Supported login ways.
# Type of two-factor authentication, if application enables it.
# Supported values are: "app" (like Google Authenticator), "sms", "email".
tfaType: app
tfaResendTimeout: 30

services:
email:
Expand Down
1 change: 1 addition & 0 deletions cmd/config-mem.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ login: # Supported login ways.
username: true
federated: true
tfaType: app
tfaResendTimeout: 30

services:
email:
Expand Down
1 change: 1 addition & 0 deletions cmd/config-mongodb.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ login: # Supported login ways.
username: true
federated: true
tfaType: app
tfaResendTimeout: 30

services:
email:
Expand Down
5 changes: 3 additions & 2 deletions model/server_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,9 @@ type RouteMobileServiceSettings struct {

// LoginSettings are settings of login.
type LoginSettings struct {
LoginWith LoginWith `yaml:"loginWith" json:"login_with"`
TFAType TFAType `yaml:"tfaType" json:"tfa_type"`
LoginWith LoginWith `yaml:"loginWith" json:"login_with"`
TFAType TFAType `yaml:"tfaType" json:"tfa_type"`
TFAResendTimeout int `yaml:"tfaResendTimeout" json:"tfa_resend_timeout"`
}

// LoginWith is a type for configuring supported login ways.
Expand Down
54 changes: 54 additions & 0 deletions web/api/2fa.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import (
"net/http"
"net/url"
"path"
"strings"
"time"

ijwt "github.com/madappgang/identifo/jwt"
"github.com/madappgang/identifo/model"
"github.com/madappgang/identifo/web/middleware"
qrcode "github.com/skip2/go-qrcode"
Expand Down Expand Up @@ -124,6 +126,58 @@ func (ar *Router) EnableTFA() http.HandlerFunc {
ar.Error(w, ErrorAPIInternalServerError, http.StatusInternalServerError, fmt.Sprintf("Unknown tfa type '%s'", ar.tfaType), "switch.tfaType")
}
}
func (ar *Router) ResendTFA() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
tfaToken, ok := r.Context().Value(model.TokenRawContextKey).([]byte)
if !ok {
ar.Error(w, ErrorAPIRequestTokenInvalid, http.StatusBadRequest, "Token bytes are not in context.", "ResendTFA.TokenBytesFromContext")
return
}

token, err := ar.server.Services().Token.Parse(string(tfaToken))
if err != nil {
ar.Error(w, ErrorAPIRequestTokenInvalid, http.StatusBadRequest, "Can't parse token.", "ResendTFA.Parse")
return
}

now := ijwt.TimeFunc().Unix()

fromIssued := now - token.IssuedAt().Unix()

if fromIssued < int64(ar.tfaResendTimeout) {
ar.Error(w, ErrorAPIRequestTokenInvalid, http.StatusBadRequest, "Please wait before new code resend.", "ResendTFA.timeout")
return
}

userID := token.Subject()
if err != nil {
ar.Error(w, ErrorAPIAppCannotExtractTokenSubject, http.StatusInternalServerError, err.Error(), "ResendTFA.getTokenSubject")
return
}

user, err := ar.server.Storages().User.UserByID(userID)
if err != nil {
ar.Error(w, ErrorAPIUserNotFound, http.StatusBadRequest, err.Error(), "ResendTFA.UserByID")
return
}

app := middleware.AppFromContext(r.Context())
if len(app.ID) == 0 {
ar.Error(w, ErrorAPIRequestAppIDInvalid, http.StatusBadRequest, "App is not in context.", "ResendTFA.AppFromContext")
return
}

authResult, err := ar.loginFlow(app, user, strings.Split(token.Scopes(), " "))
if err != nil {
ar.Error(w, ErrorAPIInternalServerError, http.StatusInternalServerError, err.Error(), "LoginWithPassword.LoginFlowError")
return
}

ar.server.Storages().Blocklist.Add(string(tfaToken))

ar.ServeJSON(w, http.StatusOK, authResult)
}
}

// FinalizeTFA finalizes two-factor authentication.
func (ar *Router) FinalizeTFA() http.HandlerFunc {
Expand Down
2 changes: 2 additions & 0 deletions web/api/app_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type appSettings struct {
RegistrationForbidden bool `json:"registrationForbidden"`
TfaType string `json:"tfaType"`
TfaStatus string `json:"tfaStatus"`
TfaResendTimeout int `json:"tfaResendTimeout"`
FederatedProviders []string `json:"federatedProviders"`
}

Expand Down Expand Up @@ -47,6 +48,7 @@ func (ar *Router) GetAppSettings() http.HandlerFunc {
RegistrationForbidden: app.RegistrationForbidden,
TfaType: string(ar.tfaType),
TfaStatus: string(app.TFAStatus),
TfaResendTimeout: ar.tfaResendTimeout,
FederatedProviders: make([]string, 0, len(app.FederatedProviders)),
}

Expand Down
21 changes: 12 additions & 9 deletions web/api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Router struct {
logger *log.Logger
router *mux.Router
tfaType model.TFAType
tfaResendTimeout int
oidcConfiguration *OIDCConfiguration
jwk *jwk
Authorizer *authorization.Authorizer
Expand All @@ -33,15 +34,16 @@ type Router struct {
}

type RouterSettings struct {
Server model.Server
Logger *log.Logger
LoggerSettings model.LoggerSettings
Authorizer *authorization.Authorizer
Host string
LoginAppPath string
TFAType model.TFAType
LoginWith model.LoginWith
Cors *cors.Cors
Server model.Server
Logger *log.Logger
LoggerSettings model.LoggerSettings
Authorizer *authorization.Authorizer
Host string
LoginAppPath string
TFAType model.TFAType
TFAResendTimeout int
LoginWith model.LoginWith
Cors *cors.Cors
}

// NewRouter creates and inits new router.
Expand All @@ -55,6 +57,7 @@ func NewRouter(settings RouterSettings) (*Router, error) {
Host: settings.Host,
LoginAppPath: settings.LoginAppPath,
tfaType: settings.TFAType,
tfaResendTimeout: settings.TFAResendTimeout,
SupportedLoginWays: settings.LoginWith,
cors: settings.Cors,
}
Expand Down
4 changes: 4 additions & 0 deletions web/api/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ func (ar *Router) initRoutes() {
ar.Token(model.TokenTypeAccess, []string{model.TokenTypeTFAPreauth}),
negroni.Wrap(ar.FinalizeTFA()),
)).Methods("POST")
auth.Path("/tfa/resend").Handler(negroni.New(
ar.Token(model.TokenTypeAccess, []string{model.TokenTypeTFAPreauth}),
negroni.Wrap(ar.ResendTFA()),
)).Methods("POST")
auth.Path("/tfa/reset").Handler(negroni.New(
ar.Token(model.TokenTypeAccess, nil),
negroni.Wrap(ar.RequestTFAReset()),
Expand Down
23 changes: 12 additions & 11 deletions web/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,16 @@ func NewRouter(settings RouterSetting) (model.Router, error) {
apiCors := cors.New(apiCorsSettings)

apiSettings := api.RouterSettings{
Server: settings.Server,
Logger: settings.Logger,
LoggerSettings: settings.LoggerSettings,
Authorizer: authorizer,
Host: settings.HostName,
LoginAppPath: loginAppPath,
LoginWith: settings.Server.Settings().Login.LoginWith,
TFAType: settings.Server.Settings().Login.TFAType,
Cors: apiCors,
Server: settings.Server,
Logger: settings.Logger,
LoggerSettings: settings.LoggerSettings,
Authorizer: authorizer,
Host: settings.HostName,
LoginAppPath: loginAppPath,
LoginWith: settings.Server.Settings().Login.LoginWith,
TFAType: settings.Server.Settings().Login.TFAType,
TFAResendTimeout: settings.Server.Settings().Login.TFAResendTimeout,
Cors: apiCors,
}

apiRouter, err := api.NewRouter(apiSettings)
Expand All @@ -66,7 +67,7 @@ func NewRouter(settings RouterSetting) (model.Router, error) {
r.APIRouter = apiRouter

if settings.Server.Settings().LoginWebApp.Type == model.FileStorageTypeNone {
r.LoginAppRouter = nil
r.LoginAppRouter = nil
} else {
// Web login app setup
loginAppSettings := spa.SPASettings{
Expand Down Expand Up @@ -142,7 +143,7 @@ func (ar *Router) setupRoutes() {
ar.RootRouter = http.NewServeMux()
ar.RootRouter.Handle("/", ar.APIRouter)
if ar.LoginAppRouter != nil {
ar.RootRouter.Handle(loginAppPath+"/", http.StripPrefix(loginAppPath, ar.LoginAppRouter))
ar.RootRouter.Handle(loginAppPath+"/", http.StripPrefix(loginAppPath, ar.LoginAppRouter))
}
if ar.AdminRouter != nil && ar.AdminPanelRouter != nil {
ar.RootRouter.Handle(adminpanelAPIPath+"/", http.StripPrefix(adminpanelAPIPath, ar.AdminRouter))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import update from '@madappgang/update-by-path';
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import Field from '~/components/shared/Field';
import { Option, Select } from '~/components/shared/Select';
import useProgressBar from '~/hooks/useProgressBar';
import { updateServerSettings } from '~/modules/settings/actions';
import { getLoginSettings } from '~/modules/settings/selectors';
import LoginTypesTable from './LoginTypesTable';
import update from "@madappgang/update-by-path";
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import Field from "~/components/shared/Field";
import Input from "~/components/shared/Input";
import { Option, Select } from "~/components/shared/Select";
import useProgressBar from "~/hooks/useProgressBar";
import { updateServerSettings } from "~/modules/settings/actions";
import { getLoginSettings } from "~/modules/settings/selectors";
import LoginTypesTable from "./LoginTypesTable";

const LoginTypesSection = () => {
const dispatch = useDispatch();
Expand All @@ -28,11 +29,16 @@ const LoginTypesSection = () => {
dispatch(updateServerSettings({ login: nextSettings }));
};

const handleInput = ({ target }) => {
const nextSettings = update(settings, {
[target.name]: Number(target.value),
});
dispatch(updateServerSettings({ login: nextSettings }));
};

return (
<section className="iap-management-section">
<p className="iap-management-section__title">
Login Types
</p>
<p className="iap-management-section__title">Login Types</p>

<p className="iap-management-section__description">
These settings allow to turn off undesirable login endpoints.
Expand All @@ -51,10 +57,18 @@ const LoginTypesSection = () => {
<Option value="email" title="Email" />
</Select>
</Field>
<Field label="2FA Resend timeout (seconds)">
<Input
name="tfaResendTimeout"
value={settings.tfaResendTimeout}
autoComplete="off"
placeholder="Timeout to show send again"
onChange={handleInput}
/>
</Field>
</div>
<LoginTypesTable types={settings.loginWith} onChange={handleChange} />
</div>

</section>
);
};
Expand Down
9 changes: 5 additions & 4 deletions web_apps_src/admin/src/modules/settings/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import {
UPDATE_SERVER_SETTINGS,
FETCH_JWT_KEYS,
SET_VERIFICATION_STATUS,
} from './types';
import authTypes from '../auth/types';
import { verificationStatuses } from '~/enums';
} from "./types";
import authTypes from "../auth/types";
import { verificationStatuses } from "~/enums";

const initialSettings = {
login: {
Expand All @@ -14,7 +14,8 @@ const initialSettings = {
federated: false,
phone: false,
},
tfaType: 'app',
tfaType: "app",
tfaLoginSettings: 30,
},
configurationStorage: null,
sessionStorage: null,
Expand Down
Loading

0 comments on commit eed433b

Please sign in to comment.