Resend account activation email (#1961)
This commit is contained in:
parent
2692c83cfe
commit
38acc4dba6
@ -86,7 +86,6 @@ func rootMutation(log *zap.Logger, service *console.Service, mailService *mailse
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Error("register: failed to create account",
|
||||
zap.String("rawSecret", secretInput),
|
||||
zap.Error(err))
|
||||
|
@ -101,6 +101,11 @@ func graphqlProject(service *console.Service, types *TypeCreator) *graphql.Objec
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = console.GetAuth(p.Context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var users []projectMember
|
||||
for _, member := range members {
|
||||
user, err := service.GetUser(p.Context, member.MemberID)
|
||||
|
@ -28,6 +28,8 @@ const (
|
||||
TokenQuery = "token"
|
||||
// ForgotPasswordQuery is a query name for password recovery request
|
||||
ForgotPasswordQuery = "forgotPassword"
|
||||
// ResendAccountActivationEmailQuery is a query name for password recovery request
|
||||
ResendAccountActivationEmailQuery = "resendAccountActivationEmail"
|
||||
)
|
||||
|
||||
// rootQuery creates query for graphql populated by AccountsClient
|
||||
@ -47,6 +49,10 @@ func rootQuery(service *console.Service, mailService *mailservice.Service, types
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = console.GetAuth(p.Context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return service.GetUser(p.Context, *id)
|
||||
},
|
||||
@ -140,6 +146,54 @@ func rootQuery(service *console.Service, mailService *mailservice.Service, types
|
||||
)
|
||||
}()
|
||||
|
||||
return true, nil
|
||||
},
|
||||
},
|
||||
ResendAccountActivationEmailQuery: &graphql.Field{
|
||||
Type: graphql.Boolean,
|
||||
Args: graphql.FieldConfigArgument{
|
||||
FieldID: &graphql.ArgumentConfig{
|
||||
Type: graphql.NewNonNull(graphql.String),
|
||||
},
|
||||
},
|
||||
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
||||
id, _ := p.Args[FieldID].(string)
|
||||
|
||||
userID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
user, err := service.GetUser(p.Context, *userID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
token, err := service.GenerateActivationToken(p.Context, user.ID, user.Email)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
rootObject := p.Info.RootValue.(map[string]interface{})
|
||||
origin := rootObject["origin"].(string)
|
||||
link := origin + rootObject[ActivationPath].(string) + token
|
||||
userName := user.ShortName
|
||||
if user.ShortName == "" {
|
||||
userName = user.FullName
|
||||
}
|
||||
|
||||
// TODO: think of a better solution
|
||||
go func() {
|
||||
_ = mailService.SendRendered(
|
||||
p.Context,
|
||||
[]post.Address{{Address: user.Email, Name: userName}},
|
||||
&AccountActivationEmail{
|
||||
Origin: origin,
|
||||
ActivationLink: link,
|
||||
},
|
||||
)
|
||||
}()
|
||||
|
||||
return true, nil
|
||||
},
|
||||
},
|
||||
|
@ -513,5 +513,33 @@ func TestGraphqlQuery(t *testing.T) {
|
||||
ok := data[consoleql.ForgotPasswordQuery].(bool)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("Resend activation email query", func(t *testing.T) {
|
||||
regToken, err := service.CreateRegToken(ctx, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
user, err := service.CreateUser(authCtx, console.CreateUser{
|
||||
UserInfo: console.UserInfo{
|
||||
FullName: "Example User",
|
||||
ShortName: "Example",
|
||||
Email: "user1@example.com",
|
||||
},
|
||||
Password: "123a123",
|
||||
}, regToken.Secret)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("query {resendAccountActivationEmail(id: \"%s\")}", user.ID)
|
||||
|
||||
result := testQuery(t, query)
|
||||
assert.NotNil(t, result)
|
||||
data := result.(map[string]interface{})
|
||||
|
||||
ok := data[consoleql.ResendAccountActivationEmailQuery].(bool)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -299,10 +299,6 @@ func (s *Service) Token(ctx context.Context, email, password string) (token stri
|
||||
// GetUser returns User by id
|
||||
func (s *Service) GetUser(ctx context.Context, id uuid.UUID) (u *User, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
_, err = GetAuth(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := s.store.Users().Get(ctx, id)
|
||||
if err != nil {
|
||||
|
@ -111,11 +111,11 @@ export async function forgotPasswordRequest(email: string): Promise<RequestRespo
|
||||
}
|
||||
|
||||
// Performs Create user graqhQL request.
|
||||
export async function createUserRequest(user: User, password: string, secret: string): Promise<RequestResponse<null>> {
|
||||
let result: RequestResponse<null> = {
|
||||
export async function createUserRequest(user: User, password: string, secret: string): Promise<RequestResponse<string>> {
|
||||
let result: RequestResponse<string> = {
|
||||
errorMessage: '',
|
||||
isSuccess: false,
|
||||
data: null
|
||||
data: ''
|
||||
};
|
||||
|
||||
let response = await apolloManager.mutate(
|
||||
@ -130,7 +130,7 @@ export async function createUserRequest(user: User, password: string, secret: st
|
||||
shortName: "${user.shortName}",
|
||||
},
|
||||
secret: "${secret}",
|
||||
){email}
|
||||
){email, id}
|
||||
}`
|
||||
),
|
||||
fetchPolicy: 'no-cache',
|
||||
@ -142,6 +142,9 @@ export async function createUserRequest(user: User, password: string, secret: st
|
||||
result.errorMessage = response.errors[0].message;
|
||||
} else {
|
||||
result.isSuccess = true;
|
||||
if (response.data) {
|
||||
result.data = response.data.createUser.id;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -250,3 +253,31 @@ export async function deleteAccountRequest(password: string): Promise<RequestRes
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function resendEmailRequest(userID: string): Promise<RequestResponse<null>> {
|
||||
let result: RequestResponse<null> = {
|
||||
errorMessage: '',
|
||||
isSuccess: false,
|
||||
data: null
|
||||
};
|
||||
|
||||
let response = await apolloManager.query(
|
||||
{
|
||||
query: gql(`
|
||||
query {
|
||||
resendAccountActivationEmail(id: "${userID}")
|
||||
}`
|
||||
),
|
||||
fetchPolicy: 'no-cache',
|
||||
errorPolicy: 'all',
|
||||
}
|
||||
);
|
||||
|
||||
if (response.errors) {
|
||||
result.errorMessage = response.errors[0].message;
|
||||
} else {
|
||||
result.isSuccess = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -8,28 +8,71 @@ import { Component, Vue } from 'vue-property-decorator';
|
||||
import Button from '@/components/common/Button.vue';
|
||||
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
|
||||
import ROUTES from '@/utils/constants/routerConstants';
|
||||
import { resendEmailRequest } from '../../api/users';
|
||||
import { getUserID } from '@/utils/consoleLocalStorage';
|
||||
|
||||
|
||||
@Component(
|
||||
{
|
||||
computed:{
|
||||
isPopupShown: function () {
|
||||
return this.$store.state.appStateModule.appState.isSuccessfulRegistrationPopupShown;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onCloseClick: function () {
|
||||
this.$store.dispatch(APP_STATE_ACTIONS.CLOSE_POPUPS);
|
||||
this.$router.push(ROUTES.LOGIN.path);
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Button,
|
||||
},
|
||||
}
|
||||
)
|
||||
{
|
||||
beforeDestroy: function() {
|
||||
if (this.$data.intervalID) {
|
||||
clearInterval(this.$data.intervalID);
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
isResendEmailButtonDisabled: true,
|
||||
timeToEnableResendEmailButton: '00:30',
|
||||
intervalID: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isPopupShown: function () {
|
||||
return this.$store.state.appStateModule.appState.isSuccessfulRegistrationPopupShown;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onResendEmailButtonClick: async function () {
|
||||
this.$data.isResendEmailButtonDisabled = true;
|
||||
|
||||
export default class RegistrationSuccessPopup extends Vue {
|
||||
let userID = getUserID();
|
||||
if (!userID) {
|
||||
return;
|
||||
}
|
||||
|
||||
let response = await resendEmailRequest(userID);
|
||||
if (response.isSuccess) {
|
||||
(this as any).startResendEmailCountdown();
|
||||
}
|
||||
},
|
||||
onCloseClick: function () {
|
||||
this.$store.dispatch(APP_STATE_ACTIONS.CLOSE_POPUPS);
|
||||
this.$router.push(ROUTES.LOGIN.path);
|
||||
},
|
||||
startResendEmailCountdown: function () {
|
||||
let countdown = 30;
|
||||
let self = this;
|
||||
this.$data.intervalID = setInterval(function () {
|
||||
countdown--;
|
||||
|
||||
let secondsLeft = countdown > 9 ? countdown : `0${countdown}`;
|
||||
self.$data.timeToEnableResendEmailButton = `00:${secondsLeft}`;
|
||||
|
||||
if (countdown <= 0) {
|
||||
clearInterval(self.$data.intervalID);
|
||||
self.$data.isResendEmailButtonDisabled = false;
|
||||
}
|
||||
}.bind(this), 1000);
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Button,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export default class RegistrationSuccessPopup extends Vue {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@ -42,6 +85,19 @@ import ROUTES from '@/utils/constants/routerConstants';
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-family: 'font_medium';
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: #354049;
|
||||
padding: 27px 0 0 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
b {
|
||||
color: #2683FF;
|
||||
}
|
||||
|
||||
a {
|
||||
font-family: 'font_bold';
|
||||
color: #2683ff;
|
||||
@ -59,6 +115,7 @@ import ROUTES from '@/utils/constants/routerConstants';
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.register-success-popup {
|
||||
width: 100%;
|
||||
max-width: 845px;
|
||||
|
@ -18,13 +18,13 @@
|
||||
<div class="register-success-popup__form-container">
|
||||
<h2 class="register-success-popup__form-container__main-label-text">Verify Your Email</h2>
|
||||
<p>You have almost finished registering your account on the Tardigrade Satellite. Only one step left. Please check your inbox for a verification email.</p>
|
||||
<!--<p>Didn’t receive a verification email?</p>-->
|
||||
<h3>Didn’t receive a verification email?<b> {{timeToEnableResendEmailButton}}</b></h3>
|
||||
<div class="register-success-popup__form-container__button-container">
|
||||
<Button label="Go to Login" width="450px" height="50px" :onPress="onCloseClick" isWhite />
|
||||
<Button label="Resend Email" width="450px" height="50px" :onPress="onResendEmailButtonClick" :isDisabled="isResendEmailButtonDisabled"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="register-success-popup__close-cross-container">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" v-on:click="onCloseClick">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" v-on:click="onCloseClick" >
|
||||
<path d="M15.7071 1.70711C16.0976 1.31658 16.0976 0.683417 15.7071 0.292893C15.3166 -0.0976311 14.6834 -0.0976311 14.2929 0.292893L15.7071 1.70711ZM0.292893 14.2929C-0.0976311 14.6834 -0.0976311 15.3166 0.292893 15.7071C0.683417 16.0976 1.31658 16.0976 1.70711 15.7071L0.292893 14.2929ZM1.70711 0.292893C1.31658 -0.0976311 0.683417 -0.0976311 0.292893 0.292893C-0.0976311 0.683417 -0.0976311 1.31658 0.292893 1.70711L1.70711 0.292893ZM14.2929 15.7071C14.6834 16.0976 15.3166 16.0976 15.7071 15.7071C16.0976 15.3166 16.0976 14.6834 15.7071 14.2929L14.2929 15.7071ZM14.2929 0.292893L0.292893 14.2929L1.70711 15.7071L15.7071 1.70711L14.2929 0.292893ZM0.292893 1.70711L14.2929 15.7071L15.7071 14.2929L1.70711 0.292893L0.292893 1.70711Z" fill="#384B65"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
15
web/satellite/src/utils/consoleLocalStorage.ts
Normal file
15
web/satellite/src/utils/consoleLocalStorage.ts
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
const localStorageConstants = {
|
||||
USER_ID: 'userID',
|
||||
USER_EMAIL: 'userEmail'
|
||||
};
|
||||
|
||||
export function setUserId(userID: string) {
|
||||
localStorage.setItem(localStorageConstants.USER_ID, userID);
|
||||
}
|
||||
|
||||
export function getUserID() {
|
||||
return localStorage.getItem(localStorageConstants.USER_ID);
|
||||
}
|
@ -13,6 +13,7 @@ import ROUTES from '../../utils/constants/routerConstants';
|
||||
import { LOADING_CLASSES } from '../../utils/constants/classConstants';
|
||||
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS } from '../../utils/constants/actionNames';
|
||||
import { createUserRequest } from '../../api/users';
|
||||
import { setUserId } from '@/utils/consoleLocalStorage';
|
||||
|
||||
@Component(
|
||||
{
|
||||
@ -97,7 +98,14 @@ import { createUserRequest } from '../../api/users';
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.data) {
|
||||
setUserId(response.data);
|
||||
}
|
||||
|
||||
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SUCCESSFUL_REGISTRATION_POPUP);
|
||||
if (this.$refs['register_success_popup'] !== null) {
|
||||
(this.$refs['register_success_popup'] as any).startResendEmailCountdown();
|
||||
}
|
||||
},
|
||||
onCreateClick: function (): any {
|
||||
let self = this as any;
|
||||
|
@ -88,5 +88,5 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<RegistrationSuccessPopup />
|
||||
<RegistrationSuccessPopup ref="register_success_popup"/>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user