Skip to content

Commit

Permalink
fix(auth): improve onSignup and onSignin webhook handler
Browse files Browse the repository at this point in the history
  • Loading branch information
getlarge committed Dec 15, 2023
1 parent deb70cc commit 7ff4f94
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 45 deletions.
2 changes: 1 addition & 1 deletion apps/auth/src/app/users/schemas/user.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class User extends UserAttrs {
})
declare email: string;

@Prop({ type: String, required: true, unique: true })
@Prop({ type: String, required: false, unique: true })
declare identityId: string;
}

Expand Down
45 changes: 13 additions & 32 deletions apps/auth/src/app/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
} from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { OryService } from '@ticketing/microservices/ory-client';
import { transactionManager } from '@ticketing/microservices/shared/mongo';
import { Model } from 'mongoose';

import { User, UserCredentials } from './models';
Expand All @@ -25,8 +24,7 @@ export class UsersService {

/**
* @description handle webhook payload sent after Ory registration and modify identity
* Unfortunately Ory's promise is not totally fulfilled. It seems impossible to trigger a blocking hooks after user registration
* This webhook is processed asynchronously so we need to make an API call to modify the identity
* Unfortunately Ory's promise is not totally fulfilled, when webhook is set to interrupt and the response is parsed, identity is not created yet (id set to 00000000-0000-0000-0000-000000000000)
*
* @see https://www.ory.sh/docs/guides/integrate-with-ory-cloud-through-webhooks#modify-identities
**/
Expand All @@ -37,50 +35,33 @@ export class UsersService {
email,
});
if (existingUser) {
await this.oryService.deleteIdentity(body.identity.id).catch((error) => {
this.logger.error(error);
});
throw new HttpException('email already used', HttpStatus.BAD_REQUEST);
}
await using manager = await transactionManager(this.userModel);
const { identity } = await manager.wrap(async () => {
const doc: Omit<User, 'id'> = {
identityId: identity.id,
email,
};
const [user] = await this.userModel.create([doc], {
session: manager.session,
});
const updatedIdentity = await this.oryService.updateIdentityMetadata(
identity.id,
{ id: user.id },
);
return { user, identity: updatedIdentity };
});
return { identity };
const result = await this.userModel.create({ email });
const user = result.toJSON<User>();
body.identity.metadata_public = { id: user.id };
return { identity: body.identity };
}

/**
* @description To workarround the issue with Ory's not offering transactional hooks, we need to check if the user exists in our database
* @description To workaround the issue with Ory's not offering transactional hooks, we need to check if the user exists in our database
* if not we use it as a second chance to create it
**/
async onSignIn(body: OnOrySignInDto): Promise<OnOrySignInDto> {
const { identity } = body;
this.logger.debug(`onSignIn`, body);
const email = identity.traits.email;
const userId = (identity.metadata_public as { id: string }).id;
const user = await this.userModel.findOne({
id: userId,
email,
});
if (!user) {
const newUser = await this.userModel.create({
identityId: identity.id,
email,
});
const updatedIdentity = await this.oryService.updateIdentityMetadata(
identity.id,
{ id: newUser.id },
);
identity.metadata_public = updatedIdentity.metadata_public;
throw new HttpException('user not found', HttpStatus.NOT_FOUND);
}
if (!user.identityId || user.identityId !== identity.id) {
user.set({ identityId: identity.id });
await user.save();
}
return { identity };
}
Expand Down
26 changes: 14 additions & 12 deletions libs/microservices/ory-client/src/lib/ory.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
Session,
} from '@ory/client';
import { CURRENT_USER_KEY } from '@ticketing/shared/constants';
import type { FastifyRequest } from 'fastify';
import type { FastifyRequest } from 'fastify/types/request';

import { OryModuleOptions } from './ory.interfaces';

Expand All @@ -26,19 +26,19 @@ export class OryService {
this.frontendApi = new FrontendApi(
new Configuration({
basePath,
})
}),
);
this.identityApi = new IdentityApi(
new Configuration({
basePath,
accessToken,
})
}),
);
this.oauth2Api = new OAuth2Api(
new Configuration({
basePath,
accessToken,
})
}),
);
}

Expand Down Expand Up @@ -78,11 +78,13 @@ export class OryService {
this.logger.error('Invalid session', session);
return false;
}
request[CURRENT_USER_KEY] = {
identityId: session.identity.id,
id: session.identity.metadata_public.id,
email: session.identity.traits.email,
};
Object.defineProperty(request, CURRENT_USER_KEY, {
value: {
identityId: session.identity.id,
id: session.identity.metadata_public.id,
email: session.identity.traits.email,
},
});
return !!session?.identity;
} catch (e) {
this.logger.error(e);
Expand All @@ -95,7 +97,7 @@ export class OryService {
// #region Identity API
async updateIdentityMetadata(
identityId: string,
metadata: Identity['metadata_public']
metadata: Identity['metadata_public'],
): Promise<Identity> {
const response = await this.identityApi.patchIdentity({
id: identityId,
Expand All @@ -117,7 +119,7 @@ export class OryService {

async toggleIdentityState(
identityId: string,
state: 'active' | 'inactive'
state: 'active' | 'inactive',
): Promise<Identity> {
const response = await this.identityApi.patchIdentity({
id: identityId,
Expand Down Expand Up @@ -148,7 +150,7 @@ export class OryService {
// TODO: allow users to create their own M2M clients
async createClient(
userId: string,
scope: string = 'openid offline'
scope: string = 'openid offline',
): Promise<OAuth2Client> {
const response = await this.oauth2Api.createOAuth2Client({
oAuth2Client: {
Expand Down

0 comments on commit 7ff4f94

Please sign in to comment.