@wtfalch/auth 0.15.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -7
- package/dist/auth.d.ts +58 -3
- package/dist/auth.js +142 -14
- package/dist/broker.d.ts +17 -1
- package/dist/broker.js +11 -2
- package/dist/browser.d.ts +11 -9
- package/dist/browser.js +163 -71
- package/dist/config.d.ts +2 -3
- package/dist/config.js +4 -4
- package/dist/cookies.d.ts +7 -0
- package/dist/cookies.js +14 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -2
- package/dist/names.d.ts +43 -0
- package/dist/names.js +146 -0
- package/dist/names.list.d.ts +19 -0
- package/dist/names.list.js +42 -0
- package/dist/names.reserved.d.ts +18 -0
- package/dist/names.reserved.js +734 -0
- package/dist/next.d.ts +24 -2
- package/dist/next.js +28 -16
- package/dist/oidc.d.ts +2 -1
- package/dist/oidc.js +21 -0
- package/package.json +11 -4
package/dist/names.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { DataSet, RegExpMatcher, collapseDuplicatesTransformer, englishDataset, englishRecommendedTransformers, parseRawPattern, resolveConfusablesTransformer, resolveLeetSpeakTransformer, skipNonAlphabeticTransformer, toAsciiLowerCaseTransformer, } from 'obscenity';
|
|
2
|
+
import reservedUsernames from 'reserved-usernames' with { type: 'json' };
|
|
3
|
+
import { NUMERIC, PHRASES, WHITELIST } from './names.list.js';
|
|
4
|
+
import { PEOPLE, SYSTEM } from './names.reserved.js';
|
|
5
|
+
export const DEFAULT_RULES = {
|
|
6
|
+
minLength: 3,
|
|
7
|
+
maxLength: 30,
|
|
8
|
+
pattern: /^[A-Za-z0-9][A-Za-z0-9._-]*$/,
|
|
9
|
+
};
|
|
10
|
+
const spellings = (entry) => [entry.word, ...(entry.also ?? [])];
|
|
11
|
+
/**
|
|
12
|
+
* Obscenity collapses repeated letters in the INPUT and matches the patterns
|
|
13
|
+
* as written, so a pattern has to be written the way its word looks after
|
|
14
|
+
* collapsing: `fitte` is met as `fite`. The English set allows two of a few
|
|
15
|
+
* letters (ni_gg_er, a_ss_); the loose matcher allows one of anything.
|
|
16
|
+
*/
|
|
17
|
+
function collapsed(word, twice) {
|
|
18
|
+
return word.replace(/(.)\1+/g, (run, letter) => twice.includes(letter) ? run.slice(0, 2) : letter);
|
|
19
|
+
}
|
|
20
|
+
const ENGLISH_TWICE = 'beolsg';
|
|
21
|
+
/** Obscenity's English words and ours, with the boundaries each asks for. */
|
|
22
|
+
const strictWords = new DataSet().addAll(englishDataset);
|
|
23
|
+
for (const entry of PHRASES) {
|
|
24
|
+
strictWords.addPhrase((phrase) => {
|
|
25
|
+
phrase.setMetadata({ originalWord: entry.word });
|
|
26
|
+
for (const word of spellings(entry)) {
|
|
27
|
+
const written = collapsed(word, ENGLISH_TWICE);
|
|
28
|
+
phrase.addPattern(parseRawPattern(entry.anywhere ? written : `|${written}|`));
|
|
29
|
+
}
|
|
30
|
+
return phrase;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
strictWords.addPhrase((phrase) => {
|
|
34
|
+
for (const term of WHITELIST)
|
|
35
|
+
phrase.addWhitelistedTerm(term);
|
|
36
|
+
return phrase;
|
|
37
|
+
});
|
|
38
|
+
const strict = new RegExpMatcher({ ...strictWords.build(), ...englishRecommendedTransformers });
|
|
39
|
+
/** Only the words that are slurs wherever they appear, read with everything between the letters skipped. */
|
|
40
|
+
const looseWords = new DataSet();
|
|
41
|
+
for (const entry of [
|
|
42
|
+
...PHRASES.filter((entry) => entry.anywhere),
|
|
43
|
+
{ word: 'nigger', also: ['nigga'] },
|
|
44
|
+
{ word: 'kike' },
|
|
45
|
+
{ word: 'retard' },
|
|
46
|
+
{ word: 'tranny' },
|
|
47
|
+
]) {
|
|
48
|
+
looseWords.addPhrase((phrase) => {
|
|
49
|
+
phrase.setMetadata({ originalWord: entry.word });
|
|
50
|
+
for (const word of spellings(entry))
|
|
51
|
+
phrase.addPattern(parseRawPattern(collapsed(word, '')));
|
|
52
|
+
return phrase;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const loose = new RegExpMatcher({
|
|
56
|
+
...looseWords.build(),
|
|
57
|
+
blacklistMatcherTransformers: [
|
|
58
|
+
resolveConfusablesTransformer(),
|
|
59
|
+
resolveLeetSpeakTransformer(),
|
|
60
|
+
toAsciiLowerCaseTransformer(),
|
|
61
|
+
skipNonAlphabeticTransformer(),
|
|
62
|
+
collapseDuplicatesTransformer({ defaultThreshold: 1 }),
|
|
63
|
+
],
|
|
64
|
+
});
|
|
65
|
+
/** What a digit or symbol stands for when it is standing for a letter. */
|
|
66
|
+
const LETTERS = {
|
|
67
|
+
'0': ['o'],
|
|
68
|
+
'1': ['i', 'l'],
|
|
69
|
+
'!': ['i', 'l'],
|
|
70
|
+
'|': ['i', 'l'],
|
|
71
|
+
'2': ['z'],
|
|
72
|
+
'3': ['e'],
|
|
73
|
+
'4': ['a'],
|
|
74
|
+
'@': ['a'],
|
|
75
|
+
'5': ['s'],
|
|
76
|
+
$: ['s'],
|
|
77
|
+
'6': ['g', 'b'],
|
|
78
|
+
'7': ['t'],
|
|
79
|
+
'8': ['b'],
|
|
80
|
+
'9': ['g'],
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Lowercase letters with the digits read as letters and nothing else, runs
|
|
84
|
+
* collapsed. Every reading, since `1` may be an `i` or an `l`; the first is
|
|
85
|
+
* the one a list entry is stored under.
|
|
86
|
+
*/
|
|
87
|
+
function flattenings(name) {
|
|
88
|
+
let forms = [''];
|
|
89
|
+
for (const char of name.toLowerCase()) {
|
|
90
|
+
const readings = /[a-z]/.test(char) ? [char] : (LETTERS[char] ?? []);
|
|
91
|
+
if (readings.length === 0)
|
|
92
|
+
continue;
|
|
93
|
+
// Bounded: a name of nothing but `1`s would otherwise double at every one.
|
|
94
|
+
const next = forms.length * readings.length > 64 ? readings.slice(0, 1) : readings;
|
|
95
|
+
forms = forms.flatMap((form) => next.map((letter) => form + letter));
|
|
96
|
+
}
|
|
97
|
+
return [...new Set(forms.map((form) => form.replace(/(.)\1+/g, '$1')))];
|
|
98
|
+
}
|
|
99
|
+
const reserved = new Set([...reservedUsernames, ...SYSTEM, ...PEOPLE].map((entry) => flattenings(entry)[0]));
|
|
100
|
+
/** The name in the shapes a word might hide in. */
|
|
101
|
+
function shapes(name) {
|
|
102
|
+
const parts = name.split(/[^A-Za-z0-9æøåÆØÅ]+/).filter(Boolean);
|
|
103
|
+
return [
|
|
104
|
+
name,
|
|
105
|
+
name.replace(/([a-zæøå])([A-ZÆØÅ])/g, '$1 $2'),
|
|
106
|
+
parts.map((part) => part.replace(/^\d+|\d+$/g, '')).join(' '),
|
|
107
|
+
parts.join(''),
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
/** The refusal as a sentence the person can act on. */
|
|
111
|
+
export function explain(reason, rules = DEFAULT_RULES) {
|
|
112
|
+
switch (reason) {
|
|
113
|
+
case 'too_short':
|
|
114
|
+
return `A username needs at least ${rules.minLength} characters.`;
|
|
115
|
+
case 'too_long':
|
|
116
|
+
return `A username can have at most ${rules.maxLength} characters.`;
|
|
117
|
+
case 'characters':
|
|
118
|
+
return 'A username is letters, digits, dots, dashes and underscores, and starts with a letter or digit.';
|
|
119
|
+
case 'reserved':
|
|
120
|
+
return 'That username is reserved.';
|
|
121
|
+
case 'disallowed':
|
|
122
|
+
return 'That username is not allowed.';
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export function checkName(name, rules = DEFAULT_RULES) {
|
|
126
|
+
const typed = name.trim();
|
|
127
|
+
if (typed.length < rules.minLength)
|
|
128
|
+
return { ok: false, reason: 'too_short' };
|
|
129
|
+
if (typed.length > rules.maxLength)
|
|
130
|
+
return { ok: false, reason: 'too_long' };
|
|
131
|
+
if (!rules.pattern.test(typed))
|
|
132
|
+
return { ok: false, reason: 'characters' };
|
|
133
|
+
const digitsKept = typed.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
134
|
+
if (NUMERIC.some((entry) => digitsKept.includes(entry)))
|
|
135
|
+
return { ok: false, reason: 'disallowed' };
|
|
136
|
+
if (loose.hasMatch(typed))
|
|
137
|
+
return { ok: false, reason: 'disallowed' };
|
|
138
|
+
if (shapes(typed).some((shape) => strict.hasMatch(shape))) {
|
|
139
|
+
return { ok: false, reason: 'disallowed' };
|
|
140
|
+
}
|
|
141
|
+
// Last, so that a number that means something (`14.88` reads as `lab`) is refused for what it is.
|
|
142
|
+
if (flattenings(typed).some((form) => reserved.has(form))) {
|
|
143
|
+
return { ok: false, reason: 'reserved' };
|
|
144
|
+
}
|
|
145
|
+
return { ok: true };
|
|
146
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What obscenity's English set does not know, and what is ours. Edit freely.
|
|
3
|
+
*
|
|
4
|
+
* `anywhere` words are refused wherever they appear, however spelled -- so
|
|
5
|
+
* only words with no innocent containing word belong there; `coon` (raccoon)
|
|
6
|
+
* and `paki` (Pakistan) do not. The rest are refused as a whole name or as a
|
|
7
|
+
* part of one split at separators and case changes: `nazi` and `big_nazi`
|
|
8
|
+
* are out, `ashkenazi` is not. `also` lists other spellings of the same word.
|
|
9
|
+
*/
|
|
10
|
+
export interface Entry {
|
|
11
|
+
word: string;
|
|
12
|
+
anywhere?: boolean;
|
|
13
|
+
also?: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare const PHRASES: Entry[];
|
|
16
|
+
/** Innocent names obscenity's English patterns would otherwise catch. */
|
|
17
|
+
export declare const WHITELIST: string[];
|
|
18
|
+
/** Read on the name with its digits kept, for the things that are numbers. */
|
|
19
|
+
export declare const NUMERIC: string[];
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const PHRASES = [
|
|
2
|
+
// en, beyond obscenity's set
|
|
3
|
+
{ word: 'faggot', anywhere: true },
|
|
4
|
+
{ word: 'chink', anywhere: true },
|
|
5
|
+
{ word: 'wetback', anywhere: true },
|
|
6
|
+
{ word: 'raghead', anywhere: true },
|
|
7
|
+
{ word: 'towelhead', anywhere: true },
|
|
8
|
+
{ word: 'hitler', anywhere: true },
|
|
9
|
+
{ word: 'gook' },
|
|
10
|
+
{ word: 'spic' },
|
|
11
|
+
{ word: 'coon' },
|
|
12
|
+
{ word: 'paki' },
|
|
13
|
+
{ word: 'nazi' },
|
|
14
|
+
{ word: 'pedo', also: ['paedo', 'pedophile', 'paedophile'] },
|
|
15
|
+
{ word: 'rapist' },
|
|
16
|
+
{ word: 'homo' },
|
|
17
|
+
// no
|
|
18
|
+
{ word: 'neger', anywhere: true },
|
|
19
|
+
{ word: 'svarting', anywhere: true },
|
|
20
|
+
{ word: 'sotrør', anywhere: true, also: ['sotror'] },
|
|
21
|
+
{ word: 'morraknuller', anywhere: true },
|
|
22
|
+
{ word: 'morrapuler', anywhere: true },
|
|
23
|
+
{ word: 'pakkis' }, // collapsed to `pakis`, it would hide in pakistan; whole word only
|
|
24
|
+
{ word: 'faen' },
|
|
25
|
+
{ word: 'fitte' },
|
|
26
|
+
{ word: 'kuk' },
|
|
27
|
+
{ word: 'pikk' },
|
|
28
|
+
{ word: 'hore' },
|
|
29
|
+
{ word: 'ræva', also: ['raeva', 'reva'] },
|
|
30
|
+
{ word: 'helvete' },
|
|
31
|
+
{ word: 'jævla', also: ['jaevla', 'jevla', 'jævlig', 'jaevlig', 'jevlig'] },
|
|
32
|
+
{ word: 'drittsekk' },
|
|
33
|
+
{ word: 'soper' },
|
|
34
|
+
{ word: 'homse' },
|
|
35
|
+
{ word: 'mongo', also: ['mongis'] },
|
|
36
|
+
{ word: 'tater' },
|
|
37
|
+
{ word: 'sigøyner', also: ['sigoyner'] },
|
|
38
|
+
];
|
|
39
|
+
/** Innocent names obscenity's English patterns would otherwise catch. */
|
|
40
|
+
export const WHITELIST = ['shiitake', 'fager'];
|
|
41
|
+
/** Read on the name with its digits kept, for the things that are numbers. */
|
|
42
|
+
export const NUMERIC = ['kkk', '1488'];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Names nobody may choose. Edit freely; order and duplicates do not matter.
|
|
3
|
+
*
|
|
4
|
+
* Both lists are compared on a flattened form: lower case, digits and symbols
|
|
5
|
+
* read as the letters they stand for, repeated letters collapsed. So `adm1n`,
|
|
6
|
+
* `Admin`, `a.d.m.i.n` and `aadmin` are all `admin`, and `K4i` is `kai`.
|
|
7
|
+
*
|
|
8
|
+
* SYSTEM is every name that would let somebody pass as us, as the software,
|
|
9
|
+
* as a mailbox that means something, or as a path an app is likely to have.
|
|
10
|
+
* The `reserved-usernames` package is applied as well, so this list is the
|
|
11
|
+
* one we can read and vouch for rather than the only one.
|
|
12
|
+
*
|
|
13
|
+
* PEOPLE are names held for particular people. They are refused like the
|
|
14
|
+
* system ones, so the person they are held for gets them by being given
|
|
15
|
+
* them, not by racing for them.
|
|
16
|
+
*/
|
|
17
|
+
export declare const SYSTEM: string[];
|
|
18
|
+
export declare const PEOPLE: string[];
|