mirror of
https://github.com/logto-io/logto.git
synced 2025-04-07 23:01:25 -05:00
feat(core): add management API to delete one-time token
This commit is contained in:
parent
ceb5098f0b
commit
76f31472f5
5 changed files with 83 additions and 2 deletions
|
@ -2,6 +2,7 @@ import { OneTimeTokens, OneTimeTokenStatus, type OneTimeToken } from '@logto/sch
|
|||
import type { CommonQueryMethods } from '@silverhand/slonik';
|
||||
import { sql } from '@silverhand/slonik';
|
||||
|
||||
import { buildDeleteByIdWithPool } from '#src/database/delete-by-id.js';
|
||||
import { buildFindEntityByIdWithPool } from '#src/database/find-entity-by-id.js';
|
||||
import { buildInsertIntoWithPool } from '#src/database/insert-into.js';
|
||||
import { convertToIdentifiers } from '#src/utils/sql.js';
|
||||
|
@ -15,6 +16,8 @@ export const createOneTimeTokenQueries = (pool: CommonQueryMethods) => {
|
|||
|
||||
const getOneTimeTokenById = buildFindEntityByIdWithPool(pool)(OneTimeTokens);
|
||||
|
||||
const deleteOneTimeTokenById = buildDeleteByIdWithPool(pool, OneTimeTokens.table);
|
||||
|
||||
const updateExpiredOneTimeTokensStatusByEmail = async (email: string) =>
|
||||
pool.query(sql`
|
||||
update ${table}
|
||||
|
@ -40,6 +43,7 @@ export const createOneTimeTokenQueries = (pool: CommonQueryMethods) => {
|
|||
`);
|
||||
|
||||
return {
|
||||
deleteOneTimeTokenById,
|
||||
insertOneTimeToken,
|
||||
updateExpiredOneTimeTokensStatusByEmail,
|
||||
getOneTimeTokenById,
|
||||
|
|
|
@ -48,6 +48,15 @@
|
|||
"description": "The one-time token found by ID."
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete one-time token by ID",
|
||||
"description": "Delete a one-time token by its ID.",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "The one-time token was deleted successfully."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/one-time-tokens/verify": {
|
||||
|
|
|
@ -17,6 +17,7 @@ export default function oneTimeTokenRoutes<T extends ManagementApiRouter>(
|
|||
{
|
||||
queries: {
|
||||
oneTimeTokens: {
|
||||
deleteOneTimeTokenById,
|
||||
insertOneTimeToken,
|
||||
updateExpiredOneTimeTokensStatusByEmail,
|
||||
getOneTimeTokenById,
|
||||
|
@ -127,4 +128,22 @@ export default function oneTimeTokenRoutes<T extends ManagementApiRouter>(
|
|||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/one-time-tokens/:id',
|
||||
koaGuard({
|
||||
params: z.object({
|
||||
id: z.string().min(1),
|
||||
}),
|
||||
status: [200, 400, 404],
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { params } = ctx.guard;
|
||||
const { id } = params;
|
||||
|
||||
await deleteOneTimeTokenById(id);
|
||||
ctx.status = 204;
|
||||
return next();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -30,3 +30,6 @@ export const updateOneTimeTokenStatus = async (id: string, status: OneTimeTokenS
|
|||
json: { status },
|
||||
})
|
||||
.json<OneTimeToken>();
|
||||
|
||||
export const deleteOneTimeTokenById = async (id: string) =>
|
||||
authedAdminApi.delete(`one-time-tokens/${id}`).json<OneTimeToken>();
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
verifyOneTimeToken,
|
||||
getOneTimeTokenById,
|
||||
updateOneTimeTokenStatus,
|
||||
deleteOneTimeTokenById,
|
||||
} from '#src/api/one-time-token.js';
|
||||
import { expectRejects } from '#src/helpers/index.js';
|
||||
import { devFeatureTest, waitFor } from '#src/utils.js';
|
||||
|
@ -25,6 +26,8 @@ describe('one-time tokens API', () => {
|
|||
expect(oneTimeToken.context).toEqual({});
|
||||
expect(oneTimeToken.email).toBe(email);
|
||||
expect(oneTimeToken.token.length).toBe(32);
|
||||
|
||||
void deleteOneTimeTokenById(oneTimeToken.id);
|
||||
});
|
||||
|
||||
it('should create one-time token with custom expiration time', async () => {
|
||||
|
@ -39,6 +42,8 @@ describe('one-time tokens API', () => {
|
|||
expect(oneTimeToken.status).toBe(OneTimeTokenStatus.Active);
|
||||
expect(oneTimeToken.email).toBe(email);
|
||||
expect(oneTimeToken.token.length).toBe(32);
|
||||
|
||||
void deleteOneTimeTokenById(oneTimeToken.id);
|
||||
});
|
||||
|
||||
it('should create one-time token with `applicationIds` and `jitOrganizationIds` configured', async () => {
|
||||
|
@ -56,6 +61,8 @@ describe('one-time tokens API', () => {
|
|||
jitOrganizationIds: ['org-1'],
|
||||
});
|
||||
expect(oneTimeToken.token.length).toBe(32);
|
||||
|
||||
void deleteOneTimeTokenById(oneTimeToken.id);
|
||||
});
|
||||
|
||||
it('should be able to get one-time token by its ID', async () => {
|
||||
|
@ -69,6 +76,8 @@ describe('one-time tokens API', () => {
|
|||
|
||||
const token = await getOneTimeTokenById(oneTimeToken.id);
|
||||
expect(token).toEqual(oneTimeToken);
|
||||
|
||||
void deleteOneTimeTokenById(oneTimeToken.id);
|
||||
});
|
||||
|
||||
it('should throw when getting a non-existent one-time token', async () => {
|
||||
|
@ -86,10 +95,13 @@ describe('one-time tokens API', () => {
|
|||
});
|
||||
|
||||
await waitFor(1001);
|
||||
await createOneTimeToken({ email });
|
||||
const newOneTimeToken = await createOneTimeToken({ email });
|
||||
|
||||
const reFetchedToken = await getOneTimeTokenById(oneTimeToken.id);
|
||||
expect(reFetchedToken.status).toBe(OneTimeTokenStatus.Expired);
|
||||
|
||||
void deleteOneTimeTokenById(oneTimeToken.id);
|
||||
void deleteOneTimeTokenById(newOneTimeToken.id);
|
||||
});
|
||||
|
||||
it('should verify one-time token', async () => {
|
||||
|
@ -122,6 +134,8 @@ describe('one-time tokens API', () => {
|
|||
status: 400,
|
||||
}
|
||||
);
|
||||
|
||||
void deleteOneTimeTokenById(oneTimeToken.id);
|
||||
});
|
||||
|
||||
it('should not succeed to verify one-time token with expired token', async () => {
|
||||
|
@ -147,6 +161,8 @@ describe('one-time tokens API', () => {
|
|||
status: 400,
|
||||
}
|
||||
);
|
||||
|
||||
void deleteOneTimeTokenById(oneTimeToken.id);
|
||||
});
|
||||
|
||||
it('should not succeed to verify one-time token wrong email', async () => {
|
||||
|
@ -168,11 +184,13 @@ describe('one-time tokens API', () => {
|
|||
status: 400,
|
||||
}
|
||||
);
|
||||
|
||||
void deleteOneTimeTokenById(oneTimeToken.id);
|
||||
});
|
||||
|
||||
it('should not succeed to verify one-time token wrong token', async () => {
|
||||
const email = `foo${generateStandardId()}@bar.com`;
|
||||
await createOneTimeToken({
|
||||
const oneTimeToken = await createOneTimeToken({
|
||||
email,
|
||||
context: {
|
||||
jitOrganizationIds: ['org-1'],
|
||||
|
@ -189,6 +207,7 @@ describe('one-time tokens API', () => {
|
|||
status: 404,
|
||||
}
|
||||
);
|
||||
void deleteOneTimeTokenById(oneTimeToken.id);
|
||||
});
|
||||
|
||||
it('should throw token_expired error and update token status to expired (token already expired but status is not updated)', async () => {
|
||||
|
@ -210,6 +229,8 @@ describe('one-time tokens API', () => {
|
|||
|
||||
const updatedToken = await getOneTimeTokenById(oneTimeToken.id);
|
||||
expect(updatedToken.status).toBe(OneTimeTokenStatus.Expired);
|
||||
|
||||
void deleteOneTimeTokenById(oneTimeToken.id);
|
||||
});
|
||||
|
||||
it('should be able to revoke a token by updating the status', async () => {
|
||||
|
@ -220,6 +241,8 @@ describe('one-time tokens API', () => {
|
|||
|
||||
const updatedToken = await getOneTimeTokenById(oneTimeToken.id);
|
||||
expect(updatedToken.status).toBe(OneTimeTokenStatus.Revoked);
|
||||
|
||||
void deleteOneTimeTokenById(oneTimeToken.id);
|
||||
});
|
||||
|
||||
it('should throw when trying to re-activate a token', async () => {
|
||||
|
@ -230,6 +253,8 @@ describe('one-time tokens API', () => {
|
|||
code: 'one_time_token.cannot_reactivate_token',
|
||||
status: 400,
|
||||
});
|
||||
|
||||
void deleteOneTimeTokenById(oneTimeToken.id);
|
||||
});
|
||||
|
||||
it('should throw when verifying a revoked token', async () => {
|
||||
|
@ -248,5 +273,26 @@ describe('one-time tokens API', () => {
|
|||
status: 400,
|
||||
}
|
||||
);
|
||||
|
||||
void deleteOneTimeTokenById(oneTimeToken.id);
|
||||
});
|
||||
|
||||
it('should throw when trying to delete a non-existent one-time token', async () => {
|
||||
await expectRejects(deleteOneTimeTokenById('non-existent-id'), {
|
||||
code: 'entity.not_found',
|
||||
status: 404,
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete the one-time token successfully', async () => {
|
||||
const email = `foo${generateStandardId()}@bar.com`;
|
||||
const oneTimeToken = await createOneTimeToken({ email });
|
||||
|
||||
await deleteOneTimeTokenById(oneTimeToken.id);
|
||||
|
||||
await expectRejects(getOneTimeTokenById(oneTimeToken.id), {
|
||||
code: 'entity.not_exists_with_id',
|
||||
status: 404,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue