moonhead 1.0.3 → 1.0.4
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 +40 -0
- package/dist/features/auth/actions.d.ts +0 -2
- package/dist/features/auth/actions.js +1 -1
- package/dist/features/auth/affiliate-api.d.ts +6 -0
- package/dist/features/auth/affiliate-api.js +1 -0
- package/dist/features/auth/signup-email.js +1 -1
- package/dist/features/settings/actions.js +1 -1
- package/dist/lib/env.d.ts +1 -0
- package/dist/lib/env.js +1 -1
- package/dist/messages/de/index.json +1 -3
- package/dist/messages/en/index.json +1 -3
- package/package.json +1 -1
- package/dist/features/auth/components/signup-form.d.ts +0 -1
- package/dist/features/auth/components/signup-form.js +0 -1
package/README.md
CHANGED
|
@@ -30,3 +30,43 @@ import { db } from "moonhead/db";
|
|
|
30
30
|
import { users } from "moonhead/db/schema";
|
|
31
31
|
import { requireSession } from "moonhead/feature/auth/authorization";
|
|
32
32
|
```
|
|
33
|
+
|
|
34
|
+
## External affiliate authentication
|
|
35
|
+
|
|
36
|
+
Set the dashboard environment variables:
|
|
37
|
+
|
|
38
|
+
```dotenv
|
|
39
|
+
APP_URL="https://dashboard.example.com"
|
|
40
|
+
AFFILIATE_SITE_URL="https://example.com"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The website must call the dashboard API directly from the browser with
|
|
44
|
+
`credentials: "include"`. The API accepts only the exact
|
|
45
|
+
`AFFILIATE_SITE_URL` origin and sets host-only, HttpOnly session cookies for
|
|
46
|
+
the dashboard.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
const response = await fetch(
|
|
50
|
+
"https://dashboard.example.com/api/affiliate/auth/login",
|
|
51
|
+
{
|
|
52
|
+
method: "POST",
|
|
53
|
+
credentials: "include",
|
|
54
|
+
headers: { "content-type": "application/json" },
|
|
55
|
+
body: JSON.stringify({ email, password, totpCode }),
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Available endpoints:
|
|
61
|
+
|
|
62
|
+
- `GET /api/affiliate/auth/config`
|
|
63
|
+
- `POST /api/affiliate/auth/login` with `email`, `password`, and optional
|
|
64
|
+
`totpCode`
|
|
65
|
+
- `POST /api/affiliate/auth/signup` with `email`, `firstName`, `lastName`,
|
|
66
|
+
`password`, `confirmPassword`, and `tosAccepted: true`
|
|
67
|
+
- `POST /api/affiliate/auth/verify-email` with `token`
|
|
68
|
+
|
|
69
|
+
Every request, including `GET`, must send `credentials: "include"`. Login
|
|
70
|
+
returns `totpRequired: true` when a second request with the TOTP code is
|
|
71
|
+
needed. Successful login and automatic activation return the dashboard URL in
|
|
72
|
+
`redirectTo`.
|
|
@@ -27,7 +27,5 @@ export declare const changePasswordAction: (args_0: FormState<{
|
|
|
27
27
|
}>>;
|
|
28
28
|
export type ResetPasswordState = FormState;
|
|
29
29
|
export declare const resetPasswordAction: (args_0: string, args_1: FormState<void>, args_2: FormData) => Promise<FormState<void>>;
|
|
30
|
-
export type SignupState = FormState;
|
|
31
|
-
export declare const signupAction: (args_0: FormState<never>, args_1: FormData) => Promise<FormState<never>>;
|
|
32
30
|
export type VerifyEmailState = FormState;
|
|
33
31
|
export declare const verifyEmailAction: (args_0: string, args_1: FormState<void>, args_2: FormData) => Promise<FormState<void>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use server";import{
|
|
1
|
+
"use server";import{and as q,eq as c}from"drizzle-orm";import{redirect as f}from"next/navigation";import{z as o}from"zod";import{db as m}from"../../db/index.js";import{canSignIn as u,SIGNUP_SOURCE as b,USER_ROLE as N,users as t}from"../../db/schema/index.js";import{parseForm as y}from"../../lib/actions/parse-form.js";import{authFormAction as O,formAction as h}from"../../lib/auth/action.js";import{hashPassword as I,verifyPassword as F}from"../../lib/auth/password.js";import{signIn as g,signOut as L}from"../../lib/auth/session.js";import{revokeAllSessionsForUser as C}from"../../lib/auth/tokens.js";import{decryptSecret as V,verifyCode as D}from"../../lib/auth/totp.js";import{clearTotpChallenge as A,issueTotpChallenge as U,readTotpChallenge as W}from"../../lib/auth/totp-challenge.js";import{consumeVerificationToken as k,inspectVerificationToken as z}from"../../lib/auth/verification-tokens.js";import{appError as s}from"../../lib/errors/codes.js";import{success as T}from"../../lib/errors/server.js";import{getPlatformSettings as x}from"../../lib/platform-settings.js";import{hydratePreferencesToCookies as v}from"../../lib/preferences/index.js";import{formDataToObject as R,PASSWORD_MAX as _,PASSWORD_MIN as P,V as S}from"../../lib/validation/zod.js";import{notifyManagersOfActivatedAffiliate as M,notifyManagersOfPendingAffiliate as j}from"../affiliates/pending-notification.js";import{sendPasswordResetEmail as G}from"./password-reset.js";const X=o.object({email:o.email().trim().toLowerCase(),password:o.string().min(P).max(_)}),St=h({},async e=>{const n=X.safeParse(R(e));if(!n.success)throw s("invalid_credentials",{field:"password"});const[i]=await m.select().from(t).where(c(t.email,n.data.email)).limit(1);if(!i?.passwordHash)throw s("invalid_credentials",{field:"password"});if(!await F(i.passwordHash,n.data.password))throw s("invalid_credentials",{field:"password"});if(i.role===N.affiliate)throw s("invalid_credentials",{field:"password"});if(!u(i.status))throw s("user_blocked",{toast:!0});if(!i.inviteAcceptedAt)throw s("email_not_verified",{toast:!0});if(i.totpConfirmedAt)return await U(i.id),{totpRequired:!0};await g(i.id),await v(i.id),(await x()).requireManagerTotp&&i.role===N.manager&&f("/user/settings"),f("/")}),$=o.object({code:o.string().regex(/^\d{6}$/)}),yt=h({},async e=>{const n=await y(e,$),i=await W();if(!i)throw s("totp_challenge_expired");const[a]=await m.select({id:t.id,email:t.email,status:t.status,totpSecret:t.totpSecret,totpConfirmedAt:t.totpConfirmedAt}).from(t).where(c(t.id,i)).limit(1);if(!a?.totpSecret||!a.totpConfirmedAt)throw await A(),s("totp_challenge_expired");if(!u(a.status))throw await A(),s("user_blocked",{toast:!0});const r=V(a.totpSecret);if(!D(r,n.code,a.email))throw s("totp_invalid_code",{field:"code"});await A(),await g(a.id),await v(a.id),f("/")}),At=async()=>{await A()};async function _t(){await L(),f("/signin")}const B=o.object({password:o.string().min(P).max(_),confirmPassword:o.string()}).refine(e=>e.password===e.confirmPassword,{path:["confirmPassword"],message:S.passwordsMustMatch}),Pt=h({},async(e,n)=>{const i=await y(n,B),a=await k(e,"invite");if(a.kind!=="ok")throw s("invalid_token");const[r]=await m.select({signupSource:t.signupSource,role:t.role,email:t.email,firstName:t.firstName,lastName:t.lastName,status:t.status}).from(t).where(c(t.id,a.userId)).limit(1);if(!r)throw s("user_not_found",{toast:!0});if(!u(r.status))throw s("user_blocked",{toast:!0});const l=await I(i.password),p=new Date,d=r.signupSource===b.affiliate_invite,H=await x(),E=d&&H.requireAffiliateApproval;if(await m.update(t).set({passwordHash:l,inviteAcceptedAt:p,...E?{}:{approvedAt:p}}).where(c(t.id,a.userId)),E){try{await j({affiliateEmail:r.email,affiliateFirstName:r.firstName,affiliateLastName:r.lastName})}catch(w){console.error("[acceptInvite] pending notification failed",{error:w instanceof Error?w.message:String(w),userId:a.userId}),f("/signup/awaiting-approval?notify=failed")}f("/signup/awaiting-approval")}if(r.role===N.affiliate)try{await M({affiliateEmail:r.email,affiliateFirstName:r.firstName,affiliateLastName:r.lastName})}catch(w){console.error("[acceptInvite] manager notification failed",{error:w instanceof Error?w.message:String(w),userId:a.userId})}await g(a.userId),await v(a.userId),f("/")}),J=o.object({email:o.email().trim().toLowerCase()}),Nt=h({},async e=>{const n=J.safeParse(R(e));if(!n.success)return;const[i]=await m.select({id:t.id,email:t.email,firstName:t.firstName,status:t.status}).from(t).where(c(t.email,n.data.email)).limit(1);if(!(!i||!u(i.status)))try{await G({userId:i.id,email:i.email,firstName:i.firstName})}catch{return T("Toasts.passwordResetEmailFailed")}}),K=o.object({password:o.string().min(P).max(_),confirmPassword:o.string()}).refine(e=>e.password===e.confirmPassword,{path:["confirmPassword"],message:S.passwordsMustMatch}),Q=o.object({currentPassword:o.string().min(1,S.fieldRequired),password:o.string().min(P,S.passwordTooShort).max(_),confirmPassword:o.string()}).refine(e=>e.password===e.confirmPassword,{path:["confirmPassword"],message:S.passwordsMustMatch}),It=O({},async({user:e,audit:n},i)=>{const a=await y(i,Q),[r]=await m.select({passwordHash:t.passwordHash}).from(t).where(c(t.id,e.id)).limit(1);if(!r?.passwordHash)throw s("invalid_credentials",{field:"currentPassword"});if(!await F(r.passwordHash,a.currentPassword))throw s("invalid_credentials",{field:"currentPassword"});const p=await I(a.password);return await m.transaction(async d=>{await d.update(t).set({passwordHash:p}).where(c(t.id,e.id)),await n({action:"user.password.change",subjectId:e.id,target:{type:"user",id:e.id,label:e.email}},d)}),await C(e.id),await g(e.id),await T("Toasts.passwordChanged")}),kt=h({},async(e,n)=>{const i=await y(n,K),a=await k(e,"password_reset");if(a.kind!=="ok")throw s("invalid_token");const[r]=await m.select({id:t.id,status:t.status}).from(t).where(c(t.id,a.userId)).limit(1);if(!r)throw s("user_not_found",{toast:!0});if(!u(r.status))throw s("user_blocked",{toast:!0});const l=await I(i.password);await m.update(t).set({passwordHash:l}).where(c(t.id,r.id)),await C(r.id),await g(r.id),await v(r.id),f("/")}),Y=o.object({token:o.string().min(1)}),xt=h({},async(e,n)=>{Y.parse({token:e});const i=await z(e,"email_verify");if(i.kind!=="ok")throw s("invalid_token");const[a]=await m.select({id:t.id,email:t.email,firstName:t.firstName,lastName:t.lastName,status:t.status}).from(t).where(q(c(t.id,i.userId),c(t.signupSource,b.self_signup))).limit(1);if(!a)throw s("invalid_token");if(!u(a.status))throw s("user_blocked",{toast:!0});if((await k(e,"email_verify")).kind!=="ok")throw s("invalid_token");const l=await x(),p=new Date;await m.update(t).set({inviteAcceptedAt:p,...l.requireAffiliateApproval?{}:{approvedAt:p}}).where(c(t.id,a.id)),l.requireAffiliateApproval&&f(await j({affiliateEmail:a.email,affiliateFirstName:a.firstName,affiliateLastName:a.lastName}).then(()=>"/signup/awaiting-approval").catch(d=>(console.error("[verifyEmail] manager notification failed",{error:d instanceof Error?d.message:String(d),userId:a.id}),"/signup/awaiting-approval?notify=failed")));try{await M({affiliateEmail:a.email,affiliateFirstName:a.firstName,affiliateLastName:a.lastName})}catch(d){console.error("[verifyEmail] manager notification failed",{error:d instanceof Error?d.message:String(d),userId:a.id})}await g(a.id),await v(a.id),f("/")});export{Pt as acceptInviteAction,At as cancelTotpChallengeAction,It as changePasswordAction,Nt as forgotPasswordAction,kt as resetPasswordAction,St as signInAction,_t as signOutAction,xt as verifyEmailAction,yt as verifyTotpAction};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import "server-only";
|
|
2
|
+
export declare function affiliateAuthOptions(request: Request): Response;
|
|
3
|
+
export declare function affiliateAuthConfig(request: Request): Response;
|
|
4
|
+
export declare function affiliateLogin(request: Request): Promise<Response>;
|
|
5
|
+
export declare function affiliateSignup(request: Request): Promise<Response>;
|
|
6
|
+
export declare function affiliateVerifyEmail(request: Request): Promise<Response>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"server-only";import{and as N,eq as m}from"drizzle-orm";import{z as s}from"zod";import{db as p}from"../../db/index.js";import{canSignIn as A,SIGNUP_SOURCE as h,USER_ROLE as R,users as n}from"../../db/schema/index.js";import{hashPassword as y,verifyPassword as L}from"../../lib/auth/password.js";import{signIn as v}from"../../lib/auth/session.js";import{decryptSecret as I,verifyCode as C}from"../../lib/auth/totp.js";import{consumeVerificationToken as U,inspectVerificationToken as x}from"../../lib/auth/verification-tokens.js";import{isUniqueViolation as T}from"../../lib/db/errors.js";import{env as d}from"../../lib/env.js";import{getPlatformSettings as O}from"../../lib/platform-settings.js";import{hydratePreferencesToCookies as S}from"../../lib/preferences/index.js";import{NAME_MAX as q,PASSWORD_MAX as k,PASSWORD_MIN as E,UserProfileSchema as b}from"../../lib/validation/zod.js";import{notifyManagersOfActivatedAffiliate as V,notifyManagersOfPendingAffiliate as F}from"../affiliates/pending-notification.js";import{sendSignupVerificationEmail as M}from"./signup-email.js";const H=s.object({email:s.email().trim().toLowerCase(),password:s.string().min(E).max(k),totpCode:s.string().regex(/^\d{6}$/).optional()}),j=b.extend({password:s.string().min(E).max(k),confirmPassword:s.string().max(q),tosAccepted:s.literal(!0)}).refine(i=>i.password===i.confirmPassword,{path:["confirmPassword"]}),D=s.object({token:s.string().min(1).max(512)});function G(){if(d.AFFILIATE_SITE_URL)return new URL(d.AFFILIATE_SITE_URL);if(d.NODE_ENV!=="production")return new URL(d.APP_URL);throw new Error("AFFILIATE_SITE_URL is required in production")}function P(i){return{"Access-Control-Allow-Credentials":"true","Access-Control-Allow-Headers":"Content-Type","Access-Control-Allow-Methods":"GET, POST, OPTIONS","Access-Control-Allow-Origin":i,"Cache-Control":"no-store",Vary:"Origin"}}function u(i){const e=i.headers.get("origin");return e===G().origin?e:null}function f(i,e,r=200){return Response.json(e,{status:r,headers:P(i)})}function o(i,e,r){return f(i,{ok:!1,error:{code:e}},r)}async function w(i){if(Number(i.headers.get("content-length")??"0")>16384)throw new Error("request_too_large");return i.json()}function oe(i){const e=u(i);return e?new Response(null,{status:204,headers:P(e)}):new Response(null,{status:403})}function se(i){const e=u(i);return e?f(e,{ok:!0,dashboardUrl:d.APP_URL}):new Response(null,{status:403})}async function fe(i){const e=u(i);if(!e)return new Response(null,{status:403});const r=H.safeParse(await w(i).catch(()=>null));if(!r.success)return o(e,"invalid_input",400);const[a]=await p.select().from(n).where(m(n.email,r.data.email)).limit(1);if(!a?.passwordHash||a.role!==R.affiliate||!await L(a.passwordHash,r.data.password))return o(e,"invalid_credentials",401);if(!A(a.status))return o(e,"user_blocked",403);if(!a.inviteAcceptedAt)return o(e,"email_not_verified",403);if(!a.approvedAt)return o(e,"affiliate_not_approved",403);if(a.totpConfirmedAt){if(!r.data.totpCode)return f(e,{ok:!1,totpRequired:!0});if(!a.totpSecret)return o(e,"user_blocked",403);const t=I(a.totpSecret);if(!C(t,r.data.totpCode,a.email))return o(e,"totp_invalid_code",401)}return await v(a.id),await S(a.id),f(e,{ok:!0,redirectTo:d.APP_URL})}async function le(i){const e=u(i);if(!e)return new Response(null,{status:403});const r=j.safeParse(await w(i).catch(()=>null));if(!r.success)return f(e,{ok:!1,error:{code:"invalid_input",fields:s.flattenError(r.error).fieldErrors}},400);const a=await y(r.data.password),[t]=await p.insert(n).values({email:r.data.email,firstName:r.data.firstName,lastName:r.data.lastName,passwordHash:a,role:R.affiliate,signupSource:h.self_signup,inviteAcceptedAt:null,tosAcceptedAt:new Date}).returning({id:n.id,email:n.email,firstName:n.firstName}).catch(l=>{if(T(l))return[];throw l});if(!t)return o(e,"email_already_exists",409);try{await M({userId:t.id,email:t.email,firstName:t.firstName})}catch(l){console.error("[affiliateSignup] verification email failed",{cause:l instanceof Error?l.message:String(l),userId:t.id})}return f(e,{ok:!0,next:"check_email"},201)}async function ce(i){const e=u(i);if(!e)return new Response(null,{status:403});const r=D.safeParse(await w(i).catch(()=>null));if(!r.success)return o(e,"invalid_input",400);const a=await x(r.data.token,"email_verify");if(a.kind!=="ok")return o(e,"invalid_token",400);const[t]=await p.select({id:n.id,email:n.email,firstName:n.firstName,lastName:n.lastName,status:n.status}).from(n).where(N(m(n.id,a.userId),m(n.signupSource,h.self_signup))).limit(1);if(!t||!A(t.status)||(await U(r.data.token,"email_verify")).kind!=="ok")return o(e,"invalid_token",400);const g=await O(),_=new Date;if(await p.update(n).set({inviteAcceptedAt:_,...g.requireAffiliateApproval?{}:{approvedAt:_}}).where(m(n.id,t.id)),g.requireAffiliateApproval){try{await F({affiliateEmail:t.email,affiliateFirstName:t.firstName,affiliateLastName:t.lastName})}catch(c){console.error("[affiliateVerifyEmail] pending notification failed",{cause:c instanceof Error?c.message:String(c),userId:t.id})}return f(e,{ok:!0,next:"awaiting_approval"})}try{await V({affiliateEmail:t.email,affiliateFirstName:t.firstName,affiliateLastName:t.lastName})}catch(c){console.error("[affiliateVerifyEmail] activation notification failed",{cause:c instanceof Error?c.message:String(c),userId:t.id})}return await v(t.id),await S(t.id),f(e,{ok:!0,next:"dashboard",redirectTo:d.APP_URL})}export{se as affiliateAuthConfig,oe as affiliateAuthOptions,fe as affiliateLogin,le as affiliateSignup,ce as affiliateVerifyEmail};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import"server-only";import{getFormatter as p,getLocale as g,getTranslations as
|
|
1
|
+
import"server-only";import{getFormatter as p,getLocale as g,getTranslations as n}from"next-intl/server";import{AffiliateSignupVerifyEmail as d}from"../../emails/affiliate/signup-verify.js";import{issueVerificationToken as u}from"../../lib/auth/verification-tokens.js";import{formatTokenExpiry as A}from"../../lib/email/format.js";import{sendEmail as y}from"../../lib/email/send.js";import{env as a}from"../../lib/env.js";async function k(r){const o=await u({userId:r.userId,purpose:"email_verify"}),i=await g(),[m,e,s]=await Promise.all([n({locale:i,namespace:"Emails.Common"}),n({locale:i,namespace:"Emails.AffiliateSignupVerify"}),p({locale:i})]),f=new URL(`/verify-email/${o.raw}`,a.AFFILIATE_SITE_URL??a.APP_URL).toString(),l=A(o.expiresAt,s),c=e("greetingNamed",{name:r.firstName}),t=a.BRAND_NAME;await y({to:r.email,subject:e("subject",{brandName:t}),react:d({verifyUrl:f,lang:i,strings:{preview:e("preview",{brandName:t}),heading:e("heading",{brandName:t}),greeting:c,body:e("body"),cta:e("cta"),expiresAtMessage:e("expiresAt",{date:l}),fallback:e("fallback"),brandName:t,footerNote:m("footerNote")}})})}export{k as sendSignupVerificationEmail};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use server";import{
|
|
1
|
+
"use server";import{redirect as p}from"next/navigation";import{db as r}from"../../db/index.js";import{USER_ROLE as f}from"../../db/schema/index.js";import{authAction as o}from"../../lib/auth/action.js";import{success as s}from"../../lib/errors/server.js";import{updatePlatformSettings as n}from"../../lib/platform-settings.js";import{bulkApprovePendingAffiliates as m}from"../affiliates/bulk-approve.js";const v=o({permission:"settings:manage",revalidate:["/settings"]},async({user:t,audit:i},a)=>(await r.transaction(async e=>{await n({requireManagerTotp:a},t.id,e),await i({action:"settings.update",target:{type:"settings",id:"requireManagerTotp"},metadata:{changed:["requireManagerTotp"]}},e)}),a&&t.role===f.manager&&!t.totpConfirmedAt&&p("/user/settings"),s("Toasts.platformSettingsUpdated"))),w=o({permission:"settings:manage",revalidate:["/settings","/affiliates"]},async({user:t,audit:i},a)=>(await r.transaction(async e=>{await n({requireAffiliateApproval:a},t.id,e),await i({action:"settings.update",target:{type:"settings",id:"requireAffiliateApproval"},metadata:{changed:["requireAffiliateApproval"]}},e)}),a||await m(t.id),s("Toasts.platformSettingsUpdated")));export{w as setRequireAffiliateApprovalAction,v as setRequireManagerTotpAction};
|
package/dist/lib/env.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ declare const EnvSchema: z.ZodObject<{
|
|
|
8
8
|
JWT_SECRET: z.ZodString;
|
|
9
9
|
TOTP_ENCRYPTION_KEY: z.ZodString;
|
|
10
10
|
APP_URL: z.ZodURL;
|
|
11
|
+
AFFILIATE_SITE_URL: z.ZodOptional<z.ZodURL>;
|
|
11
12
|
BRAND_NAME: z.ZodDefault<z.ZodString>;
|
|
12
13
|
TRACKER_BASE_URL: z.ZodURL;
|
|
13
14
|
CLICKHOUSE_URL: z.ZodOptional<z.ZodURL>;
|
package/dist/lib/env.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{z as e}from"zod";const t=e.object({NODE_ENV:e.enum(["development","production"]).default("development"),DATABASE_URL:e.url(),JWT_SECRET:e.string().regex(/^[A-Za-z0-9_-]{64,}$/,"must be \u226564 base64url chars (use crypto.randomBytes(48).toString('base64url'))"),TOTP_ENCRYPTION_KEY:e.string().regex(/^[A-Za-z0-9_-]{43}$/,"must be 32 base64url-encoded bytes (use crypto.randomBytes(32).toString('base64url'))"),APP_URL:e.url(),BRAND_NAME:e.string().trim().min(1).default("Myfiliate"),TRACKER_BASE_URL:e.url(),CLICKHOUSE_URL:e.url().optional(),CLICKHOUSE_DATABASE:e.string().default("analytics"),CLICKHOUSE_USER:e.string().optional(),CLICKHOUSE_PASSWORD:e.string().optional()}),n=process.env.SKIP_ENV_VALIDATION==="1",s=n?t.partial():t,r=s.safeParse(process.env);if(!r.success)throw new Error(`Invalid environment variables: ${JSON.stringify(e.treeifyError(r.error))}`);const a=r.data;export{a as env};
|
|
1
|
+
import{z as e}from"zod";const t=e.object({NODE_ENV:e.enum(["development","production"]).default("development"),DATABASE_URL:e.url(),JWT_SECRET:e.string().regex(/^[A-Za-z0-9_-]{64,}$/,"must be \u226564 base64url chars (use crypto.randomBytes(48).toString('base64url'))"),TOTP_ENCRYPTION_KEY:e.string().regex(/^[A-Za-z0-9_-]{43}$/,"must be 32 base64url-encoded bytes (use crypto.randomBytes(32).toString('base64url'))"),APP_URL:e.url(),AFFILIATE_SITE_URL:e.url().optional(),BRAND_NAME:e.string().trim().min(1).default("Myfiliate"),TRACKER_BASE_URL:e.url(),CLICKHOUSE_URL:e.url().optional(),CLICKHOUSE_DATABASE:e.string().default("analytics"),CLICKHOUSE_USER:e.string().optional(),CLICKHOUSE_PASSWORD:e.string().optional()}),n=process.env.SKIP_ENV_VALIDATION==="1",s=n?t.partial():t,r=s.safeParse(process.env);if(!r.success)throw new Error(`Invalid environment variables: ${JSON.stringify(e.treeifyError(r.error))}`);const a=r.data;export{a as env};
|
|
@@ -725,9 +725,7 @@
|
|
|
725
725
|
"totpCodeLabel": "Bestätigungscode",
|
|
726
726
|
"totpCodeHelp": "Öffne deine Authenticator-App und gib den aktuellen Code ein.",
|
|
727
727
|
"totpSubmit": "Bestätigen",
|
|
728
|
-
"totpBack": "Zurück zur Anmeldung"
|
|
729
|
-
"noAccount": "Neu hier?",
|
|
730
|
-
"signupLink": "Affiliate-Konto erstellen"
|
|
728
|
+
"totpBack": "Zurück zur Anmeldung"
|
|
731
729
|
},
|
|
732
730
|
"SignUp": {
|
|
733
731
|
"title": "Konto erstellen",
|
|
@@ -725,9 +725,7 @@
|
|
|
725
725
|
"totpCodeLabel": "Verification code",
|
|
726
726
|
"totpCodeHelp": "Open your authenticator app and enter the current code.",
|
|
727
727
|
"totpSubmit": "Verify",
|
|
728
|
-
"totpBack": "Back to sign-in"
|
|
729
|
-
"noAccount": "New here?",
|
|
730
|
-
"signupLink": "Create an affiliate account"
|
|
728
|
+
"totpBack": "Back to sign-in"
|
|
731
729
|
},
|
|
732
730
|
"SignUp": {
|
|
733
731
|
"title": "Create your account",
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function SignupForm(): import("react").JSX.Element;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useTranslations as n}from"next-intl";import{useActionState as c}from"react";import{ActionForm as f}from"../../../components/ui/action-form.js";import{Button as F}from"../../../components/ui/button.js";import{Checkbox as b}from"../../../components/ui/checkbox.js";import{Field as g,FieldError as x,FieldLabel as w}from"../../../components/ui/field.js";import{FormField as a}from"../../../components/ui/form-field.js";import{Input as m}from"../../../components/ui/input.js";import{PasswordInput as s}from"../../../components/ui/password-input.js";import{TextLink as d}from"../../../components/ui/text-link.js";import{signupAction as N}from"../actions.js";function B(){const r=n("Auth.SignUp"),i=n("Common"),[p,u,l]=c(N,void 0);return t(f,{action:u,actionState:p,className:"flex flex-col gap-4",children:[e(a,{name:"email",label:r("emailLabel"),children:e(m,{name:"email",type:"email","aria-required":"true",autoComplete:"email"})}),t("div",{className:"grid gap-4 sm:grid-cols-2",children:[e(a,{name:"firstName",label:i("firstName"),children:e(m,{name:"firstName",type:"text","aria-required":"true",autoComplete:"given-name"})}),e(a,{name:"lastName",label:i("lastName"),children:e(m,{name:"lastName",type:"text","aria-required":"true",autoComplete:"family-name"})})]}),e(a,{name:"password",label:r("passwordLabel"),children:e(s,{name:"password","aria-required":"true",autoComplete:"new-password"})}),e(a,{name:"confirmPassword",label:r("confirmPasswordLabel"),children:e(s,{name:"confirmPassword","aria-required":"true",autoComplete:"new-password"})}),t(g,{name:"tosAccepted",className:"flex-row items-start gap-2",children:[e(b,{name:"tosAccepted",value:"on","aria-required":"true",className:"mt-0.5"}),t("div",{className:"flex flex-col",children:[e(w,{className:"font-normal text-sm text-muted-foreground",children:r.rich("tosAccept",{terms:o=>e(d,{href:"/legal/terms",target:"_blank",rel:"noreferrer",children:o}),privacy:o=>e(d,{href:"/legal/privacy",target:"_blank",rel:"noreferrer",children:o})})}),e(x,{})]})]}),e(F,{type:"submit",disabled:l,children:r(l?"submitting":"submit")})]})}export{B as SignupForm};
|