Skip to content

Commit

Permalink
Add methods and events for email verification and password reset (#1048)
Browse files Browse the repository at this point in the history
## Description

- Add `email_verification.created` and `password_reset.created` events
- Add `GET /user_management/email_verification/:id` endpoint
- Add `GET /user_management/password_reset/:id` and `POST
/user_management/password_reset` endpoints
- Deprecate current send password reset endpoint

## Documentation

Does this require changes to the WorkOS Docs? E.g. the [API
Reference](https://workos.com/docs/reference) or code snippets need
updates.

```
[x] Yes
```

If yes, link a related docs PR and add a docs maintainer as a reviewer.
Their approval is required.

workos/workos#27414
  • Loading branch information
blairworkos authored May 23, 2024
1 parent 6902fa5 commit a57345f
Show file tree
Hide file tree
Showing 16 changed files with 346 additions and 2 deletions.
29 changes: 29 additions & 0 deletions src/common/interfaces/event.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ import {
} from '../../organizations/interfaces';
import { Connection, ConnectionResponse } from '../../sso/interfaces';
import {
EmailVerificationEvent,
EmailVerificationEventResponse,
InvitationEvent,
InvitationEventResponse,
MagicAuthEvent,
MagicAuthEventResponse,
PasswordResetEvent,
PasswordResetEventResponse,
Session,
SessionResponse,
User,
Expand Down Expand Up @@ -196,6 +200,17 @@ export interface DsyncUserUpdatedEventResponse extends EventResponseBase {
data: DirectoryUserResponse & Record<'previous_attributes', any>;
}

export interface EmailVerificationCreatedEvent extends EventBase {
event: 'email_verification.created';
data: EmailVerificationEvent;
}

export interface EmailVerificationCreatedEventResponse
extends EventResponseBase {
event: 'email_verification.created';
data: EmailVerificationEventResponse;
}

export interface InvitationCreatedEvent extends EventBase {
event: 'invitation.created';
data: InvitationEvent;
Expand All @@ -216,6 +231,16 @@ export interface MagicAuthCreatedEventResponse extends EventResponseBase {
data: MagicAuthEventResponse;
}

export interface PasswordResetCreatedEvent extends EventBase {
event: 'password_reset.created';
data: PasswordResetEvent;
}

export interface PasswordResetCreatedEventResponse extends EventResponseBase {
event: 'password_reset.created';
data: PasswordResetEventResponse;
}

export interface UserCreatedEvent extends EventBase {
event: 'user.created';
data: User;
Expand Down Expand Up @@ -387,8 +412,10 @@ export type Event =
| DsyncUserCreatedEvent
| DsyncUserUpdatedEvent
| DsyncUserDeletedEvent
| EmailVerificationCreatedEvent
| InvitationCreatedEvent
| MagicAuthCreatedEvent
| PasswordResetCreatedEvent
| UserCreatedEvent
| UserUpdatedEvent
| UserDeletedEvent
Expand Down Expand Up @@ -419,8 +446,10 @@ export type EventResponse =
| DsyncUserCreatedEventResponse
| DsyncUserUpdatedEventResponse
| DsyncUserDeletedEventResponse
| EmailVerificationCreatedEventResponse
| InvitationCreatedEventResponse
| MagicAuthCreatedEventResponse
| PasswordResetCreatedEventResponse
| UserCreatedEventResponse
| UserUpdatedEventResponse
| UserDeletedEventResponse
Expand Down
14 changes: 14 additions & 0 deletions src/common/serializers/event.serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import {
import { deserializeOrganization } from '../../organizations/serializers';
import { deserializeConnection } from '../../sso/serializers';
import {
deserializeEmailVerificationEvent,
deserializeInvitationEvent,
deserializeMagicAuthEvent,
deserializePasswordResetEvent,
deserializeUser,
} from '../../user-management/serializers';
import { deserializeOrganizationMembership } from '../../user-management/serializers/organization-membership.serializer';
Expand Down Expand Up @@ -83,6 +85,12 @@ export const deserializeEvent = (event: EventResponse): Event => {
event: event.event,
data: deserializeUpdatedEventDirectoryUser(event.data),
};
case 'email_verification.created':
return {
...eventBase,
event: event.event,
data: deserializeEmailVerificationEvent(event.data),
};
case 'invitation.created':
return {
...eventBase,
Expand All @@ -95,6 +103,12 @@ export const deserializeEvent = (event: EventResponse): Event => {
event: event.event,
data: deserializeMagicAuthEvent(event.data),
};
case 'password_reset.created':
return {
...eventBase,
event: event.event,
data: deserializePasswordResetEvent(event.data),
};
case 'user.created':
case 'user.updated':
case 'user.deleted':
Expand Down
10 changes: 10 additions & 0 deletions src/user-management/fixtures/email_verification.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"object": "email_verification",
"id": "email_verification_01H5JQDV7R7ATEYZDEG0W5PRYS",
"user_id": "user_01H5JQDV7R7ATEYZDEG0W5PRYS",
"email": "[email protected]",
"expires_at": "2023-07-18T02:07:19.911Z",
"code": "123456",
"created_at": "2023-07-18T02:07:19.911Z",
"updated_at": "2023-07-18T02:07:19.911Z"
}
2 changes: 1 addition & 1 deletion src/user-management/fixtures/invitation.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"organization_id": "org_01H5JQDV7R7ATEYZDEG0W5PRYS",
"inviter_user_id": null,
"token": "Z1uX3RbwcIl5fIGJJJCXXisdI",
"accept_invitation_url": "https://myauthkit.com/invite?invitation_token=Z1uX3RbwcIl5fIGJJJCXXisdI",
"accept_invitation_url": "https://your-app.com/invite?invitation_token=Z1uX3RbwcIl5fIGJJJCXXisdI",
"created_at": "2023-07-18T02:07:19.911Z",
"updated_at": "2023-07-18T02:07:19.911Z"
}
2 changes: 1 addition & 1 deletion src/user-management/fixtures/list-invitations.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"organization_id": "org_01H5JQDV7R7ATEYZDEG0W5PRYS",
"inviter_user_id": null,
"token": "Z1uX3RbwcIl5fIGJJJCXXisdI",
"accept_invitation_url": "https://myauthkit.com/invite?invitation_token=Z1uX3RbwcIl5fIGJJJCXXisdI",
"accept_invitation_url": "https://your-app.com/invite?invitation_token=Z1uX3RbwcIl5fIGJJJCXXisdI",
"created_at": "2023-07-18T02:07:19.911Z",
"updated_at": "2023-07-18T02:07:19.911Z"
}
Expand Down
10 changes: 10 additions & 0 deletions src/user-management/fixtures/password_reset.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"object": "password_reset",
"id": "password_reset_01H5JQDV7R7ATEYZDEG0W5PRYS",
"user_id": "user_01H5JQDV7R7ATEYZDEG0W5PRYS",
"email": "[email protected]",
"password_reset_token": "Z1uX3RbwcIl5fIGJJJCXXisdI",
"password_reset_url": "https://your-app.com/reset-password?token=Z1uX3RbwcIl5fIGJJJCXXisdI",
"expires_at": "2023-07-18T02:07:19.911Z",
"created_at": "2023-07-18T02:07:19.911Z"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface CreatePasswordResetOptions {
email: string;
}

export interface SerializedCreatePasswordResetOptions {
email: string;
}
41 changes: 41 additions & 0 deletions src/user-management/interfaces/email-verification.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
export interface EmailVerification {
object: 'email_verification';
id: string;
userId: string;
email: string;
expiresAt: string;
code: string;
createdAt: string;
updatedAt: string;
}

export interface EmailVerificationEvent {
object: 'email_verification';
id: string;
userId: string;
email: string;
expiresAt: string;
createdAt: string;
updatedAt: string;
}

export interface EmailVerificationResponse {
object: 'email_verification';
id: string;
user_id: string;
email: string;
expires_at: string;
code: string;
created_at: string;
updated_at: string;
}

export interface EmailVerificationEventResponse {
object: 'email_verification';
id: string;
user_id: string;
email: string;
expires_at: string;
created_at: string;
updated_at: string;
}
3 changes: 3 additions & 0 deletions src/user-management/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export * from './authenticate-with-totp-options.interface';
export * from './authentication-response.interface';
export * from './create-magic-auth-options.interface';
export * from './create-organization-membership-options.interface';
export * from './create-password-reset-options.interface';
export * from './create-user-options.interface';
export * from './email-verification.interface';
export * from './enroll-auth-factor.interface';
export * from './factor.interface';
export * from './impersonator.interface';
Expand All @@ -22,6 +24,7 @@ export * from './list-organization-memberships-options.interface';
export * from './list-users-options.interface';
export * from './magic-auth.interface';
export * from './organization-membership.interface';
export * from './password-reset.interface';
export * from './reset-password-options.interface';
export * from './revoke-session-options.interface';
export * from './send-invitation-options.interface';
Expand Down
39 changes: 39 additions & 0 deletions src/user-management/interfaces/password-reset.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export interface PasswordReset {
object: 'password_reset';
id: string;
userId: string;
email: string;
passwordResetToken: string;
passwordResetUrl: string;
expiresAt: string;
createdAt: string;
}

export interface PasswordResetEvent {
object: 'password_reset';
id: string;
userId: string;
email: string;
expiresAt: string;
createdAt: string;
}

export interface PasswordResetResponse {
object: 'password_reset';
id: string;
user_id: string;
email: string;
password_reset_token: string;
password_reset_url: string;
expires_at: string;
created_at: string;
}

export interface PasswordResetEventResponse {
object: 'password_reset';
id: string;
user_id: string;
email: string;
expires_at: string;
created_at: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {
CreatePasswordResetOptions,
SerializedCreatePasswordResetOptions,
} from '../interfaces';

export const serializeCreatePasswordResetOptions = (
options: CreatePasswordResetOptions,
): SerializedCreatePasswordResetOptions => ({
email: options.email,
});
31 changes: 31 additions & 0 deletions src/user-management/serializers/email-verification.serializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
EmailVerification,
EmailVerificationEvent,
EmailVerificationEventResponse,
EmailVerificationResponse,
} from '../interfaces';

export const deserializeEmailVerification = (
emailVerification: EmailVerificationResponse,
): EmailVerification => ({
object: emailVerification.object,
id: emailVerification.id,
userId: emailVerification.user_id,
email: emailVerification.email,
expiresAt: emailVerification.expires_at,
code: emailVerification.code,
createdAt: emailVerification.created_at,
updatedAt: emailVerification.updated_at,
});

export const deserializeEmailVerificationEvent = (
emailVerification: EmailVerificationEventResponse,
): EmailVerificationEvent => ({
object: emailVerification.object,
id: emailVerification.id,
userId: emailVerification.user_id,
email: emailVerification.email,
expiresAt: emailVerification.expires_at,
createdAt: emailVerification.created_at,
updatedAt: emailVerification.updated_at,
});
3 changes: 3 additions & 0 deletions src/user-management/serializers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ export * from './authenticate-with-refresh-token.options.serializer';
export * from './authenticate-with-totp-options.serializer';
export * from './authentication-response.serializer';
export * from './create-magic-auth-options.serializer';
export * from './create-password-reset-options.serializer';
export * from './email-verification.serializer';
export * from './enroll-auth-factor-options.serializer';
export * from './factor.serializer';
export * from './invitation.serializer';
export * from './magic-auth.serializer';
export * from './password-reset.serializer';
export * from './reset-password-options.serializer';
export * from './send-password-reset-email.serializer';
export * from './create-user-options.serializer';
Expand Down
30 changes: 30 additions & 0 deletions src/user-management/serializers/password-reset.serializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {
PasswordReset,
PasswordResetEvent,
PasswordResetEventResponse,
PasswordResetResponse,
} from '../interfaces';

export const deserializePasswordReset = (
passwordReset: PasswordResetResponse,
): PasswordReset => ({
object: passwordReset.object,
id: passwordReset.id,
userId: passwordReset.user_id,
email: passwordReset.email,
passwordResetToken: passwordReset.password_reset_token,
passwordResetUrl: passwordReset.password_reset_url,
expiresAt: passwordReset.expires_at,
createdAt: passwordReset.created_at,
});

export const deserializePasswordResetEvent = (
passwordReset: PasswordResetEventResponse,
): PasswordResetEvent => ({
object: passwordReset.object,
id: passwordReset.id,
userId: passwordReset.user_id,
email: passwordReset.email,
expiresAt: passwordReset.expires_at,
createdAt: passwordReset.created_at,
});
Loading

0 comments on commit a57345f

Please sign in to comment.