@oussemasahbeni/keycloakify-login-shadcn 250004.0.8 → 250004.0.10
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 +317 -0
- package/keycloak-theme/components/ui/alert.tsx +69 -61
- package/keycloak-theme/components/ui/button.tsx +44 -38
- package/keycloak-theme/components/ui/card.tsx +60 -45
- package/keycloak-theme/components/ui/checkbox.tsx +24 -22
- package/keycloak-theme/components/ui/dropdown-menu.tsx +231 -176
- package/keycloak-theme/components/ui/field.tsx +51 -48
- package/keycloak-theme/components/ui/input-otp.tsx +56 -49
- package/keycloak-theme/components/ui/input.tsx +18 -21
- package/keycloak-theme/components/ui/label.tsx +19 -20
- package/keycloak-theme/components/ui/radio-group.tsx +27 -25
- package/keycloak-theme/components/ui/select.tsx +160 -121
- package/keycloak-theme/components/ui/separator.tsx +23 -23
- package/keycloak-theme/components/ui/tooltip.tsx +54 -24
- package/keycloak-theme/login/KcPage.tsx +0 -1
- package/keycloak-theme/login/components/Template/Template.tsx +2 -2
- package/keycloak-theme/login/components/Template/useInitializeTemplate.ts +3 -19
- package/keycloak-theme/login/index.css +3 -20
- package/keycloak-theme/login/pages/login/Form.tsx +49 -51
- package/keycloak-theme/login/pages/login/SocialProviders.tsx +9 -4
- package/keycloak-theme/login/pages/login/providers/github.svg +4 -3
- package/keycloak-theme/login/pages/login/providers/x.svg +4 -3
- package/keycloak-theme/login/pages/login/useProviderLogos.tsx +2 -3
- package/keycloak-theme/login/styleLevelCustomization.tsx +1 -0
- package/keycloak-theme/public/keycloak-theme/login/js/authChecker.js +95 -0
- package/keycloak-theme/public/keycloak-theme/login/js/passkeysConditionalAuth.js +86 -0
- package/keycloak-theme/public/keycloak-theme/login/js/rfc4648.js +185 -0
- package/keycloak-theme/public/keycloak-theme/login/js/webauthnAuthenticate.js +113 -0
- package/keycloak-theme/public/keycloak-theme/login/js/webauthnRegister.js +153 -0
- package/package.json +1 -1
|
@@ -49,8 +49,8 @@ export function Form() {
|
|
|
49
49
|
{!kcContext.realm.loginWithEmailAllowed
|
|
50
50
|
? msg("email")
|
|
51
51
|
: !kcContext.realm.registrationEmailAsUsername
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
? msg("usernameOrEmail")
|
|
53
|
+
: msg("username")}
|
|
54
54
|
</FieldLabel>
|
|
55
55
|
<Input
|
|
56
56
|
tabIndex={2}
|
|
@@ -69,21 +69,21 @@ export function Form() {
|
|
|
69
69
|
"username",
|
|
70
70
|
"password"
|
|
71
71
|
) && (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
72
|
+
<FieldError>
|
|
73
|
+
<span
|
|
74
|
+
id="input-error"
|
|
75
|
+
aria-live="polite"
|
|
76
|
+
dangerouslySetInnerHTML={{
|
|
77
|
+
__html: kcSanitize(
|
|
78
|
+
kcContext.messagesPerField.getFirstError(
|
|
79
|
+
"username",
|
|
80
|
+
"password"
|
|
81
|
+
)
|
|
81
82
|
)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
)}
|
|
83
|
+
}}
|
|
84
|
+
/>
|
|
85
|
+
</FieldError>
|
|
86
|
+
)}
|
|
87
87
|
</Field>
|
|
88
88
|
)}
|
|
89
89
|
|
|
@@ -108,46 +108,44 @@ export function Form() {
|
|
|
108
108
|
"username",
|
|
109
109
|
"password"
|
|
110
110
|
) && (
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
111
|
+
<FieldError>
|
|
112
|
+
<span
|
|
113
|
+
id="input-error"
|
|
114
|
+
aria-live="polite"
|
|
115
|
+
dangerouslySetInnerHTML={{
|
|
116
|
+
__html: kcSanitize(
|
|
117
|
+
kcContext.messagesPerField.getFirstError(
|
|
118
|
+
"username",
|
|
119
|
+
"password"
|
|
120
|
+
)
|
|
120
121
|
)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
)}
|
|
122
|
+
}}
|
|
123
|
+
/>
|
|
124
|
+
</FieldError>
|
|
125
|
+
)}
|
|
126
126
|
</Field>
|
|
127
127
|
|
|
128
128
|
<div className=" space-y-1 my-3 flex justify-between text-xs ">
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
<
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
/>
|
|
129
|
+
{kcContext.realm.rememberMe &&
|
|
130
|
+
!kcContext.usernameHidden && (
|
|
131
|
+
<div className="flex items-center space-x-2 ">
|
|
132
|
+
<Checkbox
|
|
133
|
+
tabIndex={5}
|
|
134
|
+
id="rememberMe"
|
|
135
|
+
name="rememberMe"
|
|
136
|
+
defaultChecked={
|
|
137
|
+
!!kcContext.login.rememberMe
|
|
138
|
+
}
|
|
139
|
+
/>
|
|
141
140
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
</div>
|
|
141
|
+
<Label
|
|
142
|
+
htmlFor="rememberMe"
|
|
143
|
+
className="text-sm font-medium cursor-pointer"
|
|
144
|
+
>
|
|
145
|
+
{msg("rememberMe")}
|
|
146
|
+
</Label>
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
151
149
|
<div className="link-style ">
|
|
152
150
|
{kcContext.realm.resetPasswordAllowed && (
|
|
153
151
|
<span className=" underline-offset-4 hover:underline">
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { cn } from '@/components/lib/utils';
|
|
1
2
|
import { Button } from "@/components/ui/button";
|
|
2
3
|
import { kcSanitize } from "@keycloakify/login-ui/kcSanitize";
|
|
3
4
|
import { clsx } from "@keycloakify/login-ui/tools/clsx";
|
|
@@ -39,7 +40,7 @@ export function SocialProviders() {
|
|
|
39
40
|
<div className="mt-px flex-auto border-t"></div>
|
|
40
41
|
</div>
|
|
41
42
|
<ul
|
|
42
|
-
className={`mt-4! grid gap-
|
|
43
|
+
className={`mt-4! grid gap-3 sm:grid-cols-1 ${(kcContext.social?.providers?.length ?? 0) > 3 ? "sm:grid-cols-2" : ""}`}
|
|
43
44
|
>
|
|
44
45
|
{kcContext.social.providers.map((...[p, , providers]) => (
|
|
45
46
|
<li key={p.alias}>
|
|
@@ -49,9 +50,9 @@ export function SocialProviders() {
|
|
|
49
50
|
className={clsx(
|
|
50
51
|
kcClsx(
|
|
51
52
|
providers.length > 3 &&
|
|
52
|
-
|
|
53
|
+
"kcFormSocialAccountGridItem"
|
|
53
54
|
),
|
|
54
|
-
"flex items-center justify-center gap-
|
|
55
|
+
"flex items-center justify-center gap-3 "
|
|
55
56
|
)}
|
|
56
57
|
type="button"
|
|
57
58
|
href={p.loginUrl}
|
|
@@ -61,7 +62,11 @@ export function SocialProviders() {
|
|
|
61
62
|
<img
|
|
62
63
|
src={providerLogos[p.alias]}
|
|
63
64
|
alt={`${p.displayName} logo`}
|
|
64
|
-
className={
|
|
65
|
+
className={cn(
|
|
66
|
+
"h-full w-auto",
|
|
67
|
+
// Invert specific icons in dark mode
|
|
68
|
+
(p.alias === "github" || p.alias === "x" || p.alias === "twitter") && "dark:invert"
|
|
69
|
+
)}
|
|
65
70
|
/>
|
|
66
71
|
) : (
|
|
67
72
|
// Fallback to the original iconClasses if the logo is not defined
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
<svg
|
|
2
|
-
<
|
|
3
|
-
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
2
|
+
<path
|
|
3
|
+
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
|
|
4
|
+
fill="currentColor" />
|
|
4
5
|
</svg>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
<path
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" role="img"
|
|
2
|
+
aria-hidden="true">
|
|
3
|
+
<path
|
|
4
|
+
d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z" />
|
|
4
5
|
</svg>
|
|
@@ -24,11 +24,10 @@ const useProviderLogos: () => Record<string, string> = () => ({
|
|
|
24
24
|
gitlab: gitlabLogo,
|
|
25
25
|
google: googleLogo,
|
|
26
26
|
instagram: instagramLogo,
|
|
27
|
-
|
|
27
|
+
linkedin: linkedinLogo,
|
|
28
28
|
microsoft: microsoftLogo,
|
|
29
29
|
oidc: oidcLogo,
|
|
30
|
-
|
|
31
|
-
"openshift-v4": openshiftLogo,
|
|
30
|
+
openshift: openshiftLogo,
|
|
32
31
|
paypal: paypalLogo,
|
|
33
32
|
slack: slackLogo,
|
|
34
33
|
stackoverflow: stackoverflowLogo,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ThemeProvider } from "@/components/theme-provider";
|
|
2
2
|
import type { ClassKey } from "@keycloakify/login-ui/useKcClsx";
|
|
3
3
|
import type { ReactNode } from "react";
|
|
4
|
+
import "./index.css";
|
|
4
5
|
import { useKcContext } from "./KcContext";
|
|
5
6
|
import { getTheme } from "./shared/getColorScheme";
|
|
6
7
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file has been claimed for ownership from @keycloakify/login-ui version 250004.6.5.
|
|
3
|
+
* To relinquish ownership and restore this file to its original content, run the following command:
|
|
4
|
+
*
|
|
5
|
+
* $ npx keycloakify own --path "login/js/authChecker.js" --public --revert
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const SESSION_POLLING_INTERVAL = 2000;
|
|
11
|
+
const AUTH_SESSION_TIMEOUT_MILLISECS = 1000;
|
|
12
|
+
const initialSession = getSession();
|
|
13
|
+
const forms = Array.from(document.forms);
|
|
14
|
+
let timeout;
|
|
15
|
+
|
|
16
|
+
// Stop polling for a session when a form is submitted to prevent unexpected redirects.
|
|
17
|
+
// This is required as Safari does not support the 'beforeunload' event properly.
|
|
18
|
+
// See: https://bugs.webkit.org/show_bug.cgi?id=219102
|
|
19
|
+
forms.forEach((form) =>
|
|
20
|
+
form.addEventListener("submit", () => stopSessionPolling()),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// Stop polling for a session when the page is unloaded to prevent unexpected redirects.
|
|
24
|
+
globalThis.addEventListener("beforeunload", () => stopSessionPolling());
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Starts polling to check if a new session was started in another context (e.g. a tab or window), and redirects to the specified URL if a session is detected.
|
|
28
|
+
* @param {string} redirectUrl - The URL to redirect to if a new session is detected.
|
|
29
|
+
*/
|
|
30
|
+
export function startSessionPolling(redirectUrl) {
|
|
31
|
+
if (initialSession) {
|
|
32
|
+
// We started with a session, so there is nothing to do, exit.
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const session = getSession();
|
|
37
|
+
|
|
38
|
+
if (!session) {
|
|
39
|
+
// No new session detected, check again later.
|
|
40
|
+
timeout = setTimeout(
|
|
41
|
+
() => startSessionPolling(redirectUrl),
|
|
42
|
+
SESSION_POLLING_INTERVAL,
|
|
43
|
+
);
|
|
44
|
+
} else {
|
|
45
|
+
// A new session was detected, redirect to the specified URL and stop polling.
|
|
46
|
+
location.href = redirectUrl;
|
|
47
|
+
stopSessionPolling();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Stops polling the session.
|
|
53
|
+
*/
|
|
54
|
+
function stopSessionPolling() {
|
|
55
|
+
if (timeout) {
|
|
56
|
+
clearTimeout(timeout);
|
|
57
|
+
timeout = undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function checkAuthSession(pageAuthSessionHash) {
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
const cookieAuthSessionHash = getKcAuthSessionHash();
|
|
64
|
+
if (
|
|
65
|
+
cookieAuthSessionHash &&
|
|
66
|
+
cookieAuthSessionHash !== pageAuthSessionHash
|
|
67
|
+
) {
|
|
68
|
+
location.reload();
|
|
69
|
+
}
|
|
70
|
+
}, AUTH_SESSION_TIMEOUT_MILLISECS);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getKcAuthSessionHash() {
|
|
74
|
+
return getCookieByName("KC_AUTH_SESSION_HASH");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getSession() {
|
|
78
|
+
return getCookieByName("KEYCLOAK_SESSION");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getCookieByName(name) {
|
|
82
|
+
for (const cookie of document.cookie.split(";")) {
|
|
83
|
+
const [key, value] = cookie.split("=").map((value) => value.trim());
|
|
84
|
+
if (key === name) {
|
|
85
|
+
return value.startsWith('"') && value.endsWith('"')
|
|
86
|
+
? value.slice(1, -1)
|
|
87
|
+
: value;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file has been claimed for ownership from @keycloakify/login-ui version 250004.6.5.
|
|
3
|
+
* To relinquish ownership and restore this file to its original content, run the following command:
|
|
4
|
+
*
|
|
5
|
+
* $ npx keycloakify own --path "login/js/passkeysConditionalAuth.js" --public --revert
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { base64url } from "./rfc4648.js";
|
|
9
|
+
import { returnSuccess, returnFailure } from "./webauthnAuthenticate.js";
|
|
10
|
+
|
|
11
|
+
export function initAuthenticate(input) {
|
|
12
|
+
// Check if WebAuthn is supported by this browser
|
|
13
|
+
if (!window.PublicKeyCredential) {
|
|
14
|
+
returnFailure(input.errmsg);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (input.isUserIdentified || typeof PublicKeyCredential.isConditionalMediationAvailable === "undefined") {
|
|
18
|
+
document.getElementById("kc-form-passkey-button").style.display = 'block';
|
|
19
|
+
} else {
|
|
20
|
+
tryAutoFillUI(input);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function doAuthenticate(input) {
|
|
25
|
+
// Check if WebAuthn is supported by this browser
|
|
26
|
+
if (!window.PublicKeyCredential) {
|
|
27
|
+
returnFailure(input.errmsg);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const publicKey = {
|
|
32
|
+
rpId : input.rpId,
|
|
33
|
+
challenge: base64url.parse(input.challenge, { loose: true })
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
publicKey.allowCredentials = !input.isUserIdentified ? [] : getAllowCredentials();
|
|
37
|
+
|
|
38
|
+
if (input.createTimeout !== 0) {
|
|
39
|
+
publicKey.timeout = input.createTimeout * 1000;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (input.userVerification !== 'not specified') {
|
|
43
|
+
publicKey.userVerification = input.userVerification;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return navigator.credentials.get({
|
|
47
|
+
publicKey: publicKey,
|
|
48
|
+
...input.additionalOptions
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function tryAutoFillUI(input) {
|
|
53
|
+
const isConditionalMediationAvailable = await PublicKeyCredential.isConditionalMediationAvailable();
|
|
54
|
+
if (isConditionalMediationAvailable) {
|
|
55
|
+
document.getElementById("kc-form-login").style.display = "block";
|
|
56
|
+
input.additionalOptions = { mediation: 'conditional'};
|
|
57
|
+
try {
|
|
58
|
+
const result = await doAuthenticate(input);
|
|
59
|
+
returnSuccess(result);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
returnFailure(error);
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
document.getElementById("kc-form-passkey-button").style.display = 'block';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getAllowCredentials() {
|
|
69
|
+
const allowCredentials = [];
|
|
70
|
+
const authnUse = document.forms['authn_select'].authn_use_chk;
|
|
71
|
+
if (authnUse !== undefined) {
|
|
72
|
+
if (authnUse.length === undefined) {
|
|
73
|
+
allowCredentials.push({
|
|
74
|
+
id: base64url.parse(authnUse.value, {loose: true}),
|
|
75
|
+
type: 'public-key',
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
authnUse.forEach((entry) =>
|
|
79
|
+
allowCredentials.push({
|
|
80
|
+
id: base64url.parse(entry.value, {loose: true}),
|
|
81
|
+
type: 'public-key',
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return allowCredentials;
|
|
86
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file has been claimed for ownership from @keycloakify/login-ui version 250004.6.5.
|
|
3
|
+
* To relinquish ownership and restore this file to its original content, run the following command:
|
|
4
|
+
*
|
|
5
|
+
* $ npx keycloakify own --path "login/js/rfc4648.js" --public --revert
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
|
|
9
|
+
function parse(string, encoding, opts) {
|
|
10
|
+
var _opts$out;
|
|
11
|
+
|
|
12
|
+
if (opts === void 0) {
|
|
13
|
+
opts = {};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Build the character lookup table:
|
|
17
|
+
if (!encoding.codes) {
|
|
18
|
+
encoding.codes = {};
|
|
19
|
+
|
|
20
|
+
for (var i = 0; i < encoding.chars.length; ++i) {
|
|
21
|
+
encoding.codes[encoding.chars[i]] = i;
|
|
22
|
+
}
|
|
23
|
+
} // The string must have a whole number of bytes:
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if (!opts.loose && string.length * encoding.bits & 7) {
|
|
27
|
+
throw new SyntaxError('Invalid padding');
|
|
28
|
+
} // Count the padding bytes:
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
var end = string.length;
|
|
32
|
+
|
|
33
|
+
while (string[end - 1] === '=') {
|
|
34
|
+
--end; // If we get a whole number of bytes, there is too much padding:
|
|
35
|
+
|
|
36
|
+
if (!opts.loose && !((string.length - end) * encoding.bits & 7)) {
|
|
37
|
+
throw new SyntaxError('Invalid padding');
|
|
38
|
+
}
|
|
39
|
+
} // Allocate the output:
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
var out = new ((_opts$out = opts.out) != null ? _opts$out : Uint8Array)(end * encoding.bits / 8 | 0); // Parse the data:
|
|
43
|
+
|
|
44
|
+
var bits = 0; // Number of bits currently in the buffer
|
|
45
|
+
|
|
46
|
+
var buffer = 0; // Bits waiting to be written out, MSB first
|
|
47
|
+
|
|
48
|
+
var written = 0; // Next byte to write
|
|
49
|
+
|
|
50
|
+
for (var _i = 0; _i < end; ++_i) {
|
|
51
|
+
// Read one character from the string:
|
|
52
|
+
var value = encoding.codes[string[_i]];
|
|
53
|
+
|
|
54
|
+
if (value === undefined) {
|
|
55
|
+
throw new SyntaxError('Invalid character ' + string[_i]);
|
|
56
|
+
} // Append the bits to the buffer:
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
buffer = buffer << encoding.bits | value;
|
|
60
|
+
bits += encoding.bits; // Write out some bits if the buffer has a byte's worth:
|
|
61
|
+
|
|
62
|
+
if (bits >= 8) {
|
|
63
|
+
bits -= 8;
|
|
64
|
+
out[written++] = 0xff & buffer >> bits;
|
|
65
|
+
}
|
|
66
|
+
} // Verify that we have received just enough bits:
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if (bits >= encoding.bits || 0xff & buffer << 8 - bits) {
|
|
70
|
+
throw new SyntaxError('Unexpected end of data');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
function stringify(data, encoding, opts) {
|
|
76
|
+
if (opts === void 0) {
|
|
77
|
+
opts = {};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
var _opts = opts,
|
|
81
|
+
_opts$pad = _opts.pad,
|
|
82
|
+
pad = _opts$pad === void 0 ? true : _opts$pad;
|
|
83
|
+
var mask = (1 << encoding.bits) - 1;
|
|
84
|
+
var out = '';
|
|
85
|
+
var bits = 0; // Number of bits currently in the buffer
|
|
86
|
+
|
|
87
|
+
var buffer = 0; // Bits waiting to be written out, MSB first
|
|
88
|
+
|
|
89
|
+
for (var i = 0; i < data.length; ++i) {
|
|
90
|
+
// Slurp data into the buffer:
|
|
91
|
+
buffer = buffer << 8 | 0xff & data[i];
|
|
92
|
+
bits += 8; // Write out as much as we can:
|
|
93
|
+
|
|
94
|
+
while (bits > encoding.bits) {
|
|
95
|
+
bits -= encoding.bits;
|
|
96
|
+
out += encoding.chars[mask & buffer >> bits];
|
|
97
|
+
}
|
|
98
|
+
} // Partial character:
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
if (bits) {
|
|
102
|
+
out += encoding.chars[mask & buffer << encoding.bits - bits];
|
|
103
|
+
} // Add padding characters until we hit a byte boundary:
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
if (pad) {
|
|
107
|
+
while (out.length * encoding.bits & 7) {
|
|
108
|
+
out += '=';
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
|
|
116
|
+
var base16Encoding = {
|
|
117
|
+
chars: '0123456789ABCDEF',
|
|
118
|
+
bits: 4
|
|
119
|
+
};
|
|
120
|
+
var base32Encoding = {
|
|
121
|
+
chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
|
|
122
|
+
bits: 5
|
|
123
|
+
};
|
|
124
|
+
var base32HexEncoding = {
|
|
125
|
+
chars: '0123456789ABCDEFGHIJKLMNOPQRSTUV',
|
|
126
|
+
bits: 5
|
|
127
|
+
};
|
|
128
|
+
var base64Encoding = {
|
|
129
|
+
chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
|
|
130
|
+
bits: 6
|
|
131
|
+
};
|
|
132
|
+
var base64UrlEncoding = {
|
|
133
|
+
chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
|
|
134
|
+
bits: 6
|
|
135
|
+
};
|
|
136
|
+
var base16 = {
|
|
137
|
+
parse: function parse$1(string, opts) {
|
|
138
|
+
return parse(string.toUpperCase(), base16Encoding, opts);
|
|
139
|
+
},
|
|
140
|
+
stringify: function stringify$1(data, opts) {
|
|
141
|
+
return stringify(data, base16Encoding, opts);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
var base32 = {
|
|
145
|
+
parse: function parse$1(string, opts) {
|
|
146
|
+
if (opts === void 0) {
|
|
147
|
+
opts = {};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return parse(opts.loose ? string.toUpperCase().replace(/0/g, 'O').replace(/1/g, 'L').replace(/8/g, 'B') : string, base32Encoding, opts);
|
|
151
|
+
},
|
|
152
|
+
stringify: function stringify$1(data, opts) {
|
|
153
|
+
return stringify(data, base32Encoding, opts);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
var base32hex = {
|
|
157
|
+
parse: function parse$1(string, opts) {
|
|
158
|
+
return parse(string, base32HexEncoding, opts);
|
|
159
|
+
},
|
|
160
|
+
stringify: function stringify$1(data, opts) {
|
|
161
|
+
return stringify(data, base32HexEncoding, opts);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
var base64 = {
|
|
165
|
+
parse: function parse$1(string, opts) {
|
|
166
|
+
return parse(string, base64Encoding, opts);
|
|
167
|
+
},
|
|
168
|
+
stringify: function stringify$1(data, opts) {
|
|
169
|
+
return stringify(data, base64Encoding, opts);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
var base64url = {
|
|
173
|
+
parse: function parse$1(string, opts) {
|
|
174
|
+
return parse(string, base64UrlEncoding, opts);
|
|
175
|
+
},
|
|
176
|
+
stringify: function stringify$1(data, opts) {
|
|
177
|
+
return stringify(data, base64UrlEncoding, opts);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
var codec = {
|
|
181
|
+
parse: parse,
|
|
182
|
+
stringify: stringify
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export { base16, base32, base32hex, base64, base64url, codec };
|