Skip to content

Commit f326bf0

Browse files
bluesky handle checking updated to be on the server side + pull from bluesky official app
1 parent 1bd7b96 commit f326bf0

File tree

2 files changed

+102
-2
lines changed

2 files changed

+102
-2
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Source:https://github.com/bluesky-social/atproto/blob/main/packages/syntax/src/handle.ts
2+
3+
// Handle constraints, in English:
4+
// - must be a possible domain name
5+
// - RFC-1035 is commonly referenced, but has been updated. eg, RFC-3696,
6+
// section 2. and RFC-3986, section 3. can now have leading numbers (eg,
7+
// 4chan.org)
8+
// - "labels" (sub-names) are made of ASCII letters, digits, hyphens
9+
// - can not start or end with a hyphen
10+
// - TLD (last component) should not start with a digit
11+
// - can't end with a hyphen (can end with digit)
12+
// - each segment must be between 1 and 63 characters (not including any periods)
13+
// - overall length can't be more than 253 characters
14+
// - separated by (ASCII) periods; does not start or end with period
15+
// - case insensitive
16+
// - domains (handles) are equal if they are the same lower-case
17+
// - punycode allowed for internationalization
18+
// - no whitespace, null bytes, joining chars, etc
19+
// - does not validate whether domain or TLD exists, or is a reserved or
20+
// special TLD (eg, .onion or .local)
21+
// - does not validate punycode
22+
export const ensureValidHandle = (handle: string): void => {
23+
// check that all chars are boring ASCII
24+
if (!/^[a-zA-Z0-9.-]*$/.test(handle)) {
25+
throw new Error(
26+
'Disallowed characters in handle (ASCII letters, digits, dashes, periods only)'
27+
);
28+
}
29+
30+
if (handle.length > 253) {
31+
throw new Error('Handle is too long (253 chars max)');
32+
}
33+
const labels = handle.split('.');
34+
if (labels.length < 2) {
35+
throw new Error('Handle domain needs at least two parts');
36+
}
37+
for (let i = 0; i < labels.length; i++) {
38+
const l = labels[i];
39+
if (l.length < 1) {
40+
throw new Error('Handle parts can not be empty');
41+
}
42+
if (l.length > 63) {
43+
throw new Error('Handle part too long (max 63 chars)');
44+
}
45+
if (l.endsWith('-') || l.startsWith('-')) {
46+
throw new Error('Handle parts can not start or end with hyphens');
47+
}
48+
if (i + 1 === labels.length && !/^[a-zA-Z]/.test(l)) {
49+
throw new Error(
50+
'Handle final component (TLD) must start with ASCII letter'
51+
);
52+
}
53+
}
54+
};
55+
56+
// simple regex translation of above constraints
57+
export const ensureValidHandleRegex = (handle: string): void => {
58+
if (
59+
!/^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/.test(
60+
handle
61+
)
62+
) {
63+
throw new Error("Handle didn't validate via regex");
64+
}
65+
if (handle.length > 253) {
66+
throw new Error('Handle is too long (253 chars max)');
67+
}
68+
};
69+
70+
export const normalizeHandle = (handle: string): string => {
71+
return handle.toLowerCase();
72+
};
73+
74+
export const normalizeAndEnsureValidHandle = (handle: string): string => {
75+
const normalized = normalizeHandle(handle);
76+
ensureValidHandle(normalized);
77+
return normalized;
78+
};
79+
80+
export const isValidHandle = (handle: string): boolean => {
81+
try {
82+
ensureValidHandle(handle);
83+
} catch (err) {
84+
console.log('Error in bluesky handle validation:', err);
85+
if (err instanceof Error) {
86+
return false;
87+
}
88+
return false;
89+
}
90+
91+
return true;
92+
};

libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import dayjs from 'dayjs';
1111
import { Integration } from '@prisma/client';
1212
import { AuthService } from '@gitroom/helpers/auth/auth.service';
1313
import sharp from 'sharp';
14+
import { isValidHandle } from '@gitroom/helpers/integrations/bluesky.provider';
1415

1516
export class BlueskyProvider extends SocialAbstract implements SocialProvider {
1617
identifier = 'bluesky';
@@ -31,7 +32,7 @@ export class BlueskyProvider extends SocialAbstract implements SocialProvider {
3132
key: 'identifier',
3233
label: 'Identifier',
3334
placeholder: 'johndoe.bsky.social',
34-
validation: `/^[a-zA-Z0-9]+\\.[a-zA-Z0-9]+\\.[a-zA-Z0-9]+$/`,
35+
validation: `/^.{3,}$/`,
3536
type: 'text' as const,
3637
},
3738
{
@@ -76,6 +77,10 @@ export class BlueskyProvider extends SocialAbstract implements SocialProvider {
7677
service: body.service,
7778
});
7879

80+
if (!isValidHandle(body.identifier)) {
81+
throw new Error('Invalid handle');
82+
}
83+
7984
const {
8085
data: { accessJwt, refreshJwt, handle, did },
8186
} = await agent.login({
@@ -97,7 +102,10 @@ export class BlueskyProvider extends SocialAbstract implements SocialProvider {
97102
username: profile.data.handle!,
98103
};
99104
} catch (error) {
100-
console.error('Error occurred in the authenticate function:', error);
105+
console.error(
106+
'Error occurred in the +Bluesky authenticate function',
107+
error
108+
);
101109
return {
102110
refreshToken: '',
103111
expiresIn: 0,

0 commit comments

Comments
 (0)