diff --git a/README.md b/README.md index ce28220..d933e78 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,9 @@ import { UserSchema } from '@seamless-auth/types'; const user = UserSchema.parse(data); ``` +User role schemas accept plain roles such as `admin` and colon-separated scoped roles such as +`admin:read` and `admin:write`. Whitespace, underscores, slashes, and backslashes are rejected. + ### Infer types ```ts diff --git a/src/schemas/user/schema.test.ts b/src/schemas/user/schema.test.ts index be5538a..1938f03 100644 --- a/src/schemas/user/schema.test.ts +++ b/src/schemas/user/schema.test.ts @@ -57,6 +57,15 @@ describe('UserSchema', () => { ).toThrow(); }); + it('allows scoped role format', () => { + expect(() => + UserSchema.parse({ + ...baseUser, + roles: ['admin:read', 'admin:write'], + }), + ).not.toThrow(); + }); + it('allows nullable lastLogin', () => { expect(() => UserSchema.parse({ @@ -84,6 +93,16 @@ describe('CreateUserSchema', () => { ).not.toThrow(); }); + it('parses scoped roles', () => { + const parsed = CreateUserSchema.parse({ + email: 'test@example.com', + phone: '1234567890', + roles: ['admin:read'], + }); + + expect(parsed.roles).toEqual(['admin:read']); + }); + it('fails if roles are empty', () => { expect(() => CreateUserSchema.parse({ @@ -148,6 +167,14 @@ describe('UpdateUserSchema', () => { ).toThrow(); }); + it('parses scoped role updates', () => { + const parsed = UpdateUserSchema.parse({ + roles: ['admin:write'], + }); + + expect(parsed.roles).toEqual(['admin:write']); + }); + it('fails on unknown fields due to strict()', () => { expect(() => UpdateUserSchema.parse({ diff --git a/src/schemas/user/schema.ts b/src/schemas/user/schema.ts index 4fa4b67..820049b 100644 --- a/src/schemas/user/schema.ts +++ b/src/schemas/user/schema.ts @@ -1,7 +1,10 @@ import { z } from 'zod'; import { IsoDate } from '../../shared.js'; -export const RoleSchema = z.string().regex(/^(?!.*[_/\\\s])[A-Za-z0-9-]{1,31}$/); +export const RoleSchema = z + .string() + .trim() + .regex(/^(?!.*[_/\\\s])(?=.{1,80}$)[A-Za-z0-9-]+(?::[A-Za-z0-9-]+)*$/); export const UserSchema = z.object({ id: z.uuid(),