@auth/solid-start 0.1.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/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2022-2023, Balázs Orbán
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.MD ADDED
@@ -0,0 +1,80 @@
1
+ # Getting started
2
+
3
+ Recommended to use [create-jd-app](https://github.com/OrJDev/create-jd-app)
4
+
5
+ ```bash
6
+ npm install @auth/solid-start@latest @auth/core@latest
7
+ ```
8
+
9
+ ## Setting It Up
10
+
11
+ [Generate auth secret](https://generate-secret.vercel.app/32), then set it as an environment variable:
12
+
13
+ ```
14
+ AUTH_SECRET=your_auth_secret
15
+ ```
16
+
17
+ ### On Production
18
+
19
+ Don't forget to trust the host.
20
+
21
+ ```
22
+ AUTH_TRUST_HOST=true
23
+ ```
24
+
25
+ ## Creating the api handler
26
+
27
+ in this example we are using github so make sure to set the following environment variables:
28
+
29
+ ```
30
+ GITHUB_ID=your_github_oatuh_id
31
+ GITHUB_SECRET=your_github_oatuh_secret
32
+ ```
33
+
34
+ ```ts
35
+ // routes/api/auth/[...solidauth].ts
36
+ import { SolidAuth, type SolidAuthConfig } from "@auth/solid-start"
37
+ import GitHub from "@auth/core/providers/github"
38
+
39
+ export const authOpts: SolidAuthConfig = {
40
+ providers: [
41
+ GitHub({
42
+ clientId: process.env.GITHUB_ID,
43
+ clientSecret: process.env.GITHUB_SECRET,
44
+ }),
45
+ ],
46
+ debug: false,
47
+ }
48
+
49
+ export const { GET, POST } = SolidAuth(authOpts)
50
+ ```
51
+
52
+ ## Signing in and out
53
+
54
+ ```ts
55
+ import { signIn, signOut } from "@auth/solid-start/client"
56
+ const login = () => signIn("github")
57
+ const logout = () => signOut()
58
+ ```
59
+
60
+ ## Getting the current session
61
+
62
+ ```ts
63
+ import { getSession } from "@auth/solid-start"
64
+ import { createServerData$ } from "solid-start/server"
65
+ import { authOpts } from "~/routes/api/auth/[...solidauth]"
66
+
67
+ export const useSession = () => {
68
+ return createServerData$(
69
+ async (_, { request }) => {
70
+ return await getSession(request, authOpts)
71
+ },
72
+ { key: () => ["auth_user"] }
73
+ )
74
+ }
75
+
76
+ // useSession returns a resource:
77
+ const session = useSession()
78
+ const loading = session.loading
79
+ const user = () => session()?.user
80
+ ```
package/client.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { LiteralUnion, SignInOptions, SignInAuthorizationParams, SignOutParams } from 'next-auth/react';
2
+ import { RedirectableProviderType, BuiltInProviderType } from '@auth/core/providers';
3
+
4
+ /**
5
+ * Client-side method to initiate a signin flow
6
+ * or send the user to the signin page listing all possible providers.
7
+ * Automatically adds the CSRF token to the request.
8
+ *
9
+ * [Documentation](https://next-auth.js.org/getting-started/client#signin)
10
+ */
11
+ declare function signIn<P extends RedirectableProviderType | undefined = undefined>(providerId?: LiteralUnion<P extends RedirectableProviderType ? P | BuiltInProviderType : BuiltInProviderType>, options?: SignInOptions, authorizationParams?: SignInAuthorizationParams): Promise<Response | undefined>;
12
+ /**
13
+ * Signs the user out, by removing the session cookie.
14
+ * Automatically adds the CSRF token to the request.
15
+ *
16
+ * [Documentation](https://next-auth.js.org/getting-started/client#signout)
17
+ */
18
+ declare function signOut(options?: SignOutParams): Promise<void>;
19
+
20
+ export { signIn, signOut };
package/client.js ADDED
@@ -0,0 +1,56 @@
1
+ async function signIn(providerId, options, authorizationParams) {
2
+ const { callbackUrl = window.location.href, redirect = true } = options ?? {};
3
+ const isCredentials = providerId === "credentials";
4
+ const isEmail = providerId === "email";
5
+ const isSupportingReturn = isCredentials || isEmail;
6
+ const signInUrl = `/api/auth/${isCredentials ? "callback" : "signin"}/${providerId}`;
7
+ const _signInUrl = `${signInUrl}?${new URLSearchParams(authorizationParams)}`;
8
+ const csrfTokenResponse = await fetch("/api/auth/csrf");
9
+ const { csrfToken } = await csrfTokenResponse.json();
10
+ const res = await fetch(_signInUrl, {
11
+ method: "post",
12
+ headers: {
13
+ "Content-Type": "application/x-www-form-urlencoded",
14
+ "X-Auth-Return-Redirect": "1"
15
+ },
16
+ body: new URLSearchParams({
17
+ ...options,
18
+ csrfToken,
19
+ callbackUrl
20
+ })
21
+ });
22
+ const data = await res.clone().json();
23
+ const error = new URL(data.url).searchParams.get("error");
24
+ if (redirect || !isSupportingReturn || !error) {
25
+ window.location.href = data.url ?? data.redirect ?? callbackUrl;
26
+ if (data.url.includes("#"))
27
+ window.location.reload();
28
+ return;
29
+ }
30
+ return res;
31
+ }
32
+ async function signOut(options) {
33
+ const { callbackUrl = window.location.href } = options ?? {};
34
+ const csrfTokenResponse = await fetch("/api/auth/csrf");
35
+ const { csrfToken } = await csrfTokenResponse.json();
36
+ const res = await fetch(`/api/auth/signout`, {
37
+ method: "post",
38
+ headers: {
39
+ "Content-Type": "application/x-www-form-urlencoded",
40
+ "X-Auth-Return-Redirect": "1"
41
+ },
42
+ body: new URLSearchParams({
43
+ csrfToken,
44
+ callbackUrl
45
+ })
46
+ });
47
+ const data = await res.json();
48
+ const url = data.url ?? data.redirect ?? callbackUrl;
49
+ window.location.href = url;
50
+ if (url.includes("#"))
51
+ window.location.reload();
52
+ }
53
+ export {
54
+ signIn,
55
+ signOut
56
+ };
package/index.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { AuthConfig, Session } from '@auth/core/types';
2
+
3
+ interface SolidAuthConfig extends AuthConfig {
4
+ /**
5
+ * Defines the base path for the auth routes.
6
+ * @default '/api/auth'
7
+ */
8
+ prefix?: string;
9
+ }
10
+ declare function SolidAuth(config: SolidAuthConfig): {
11
+ GET(event: any): Promise<Response | undefined>;
12
+ POST(event: any): Promise<Response | undefined>;
13
+ };
14
+ type GetSessionResult = Promise<Session | null>;
15
+ declare function getSession(req: Request, options: AuthConfig): GetSessionResult;
16
+
17
+ export { GetSessionResult, SolidAuth, SolidAuthConfig, getSession };
package/index.js ADDED
@@ -0,0 +1,87 @@
1
+ import { Auth } from "@auth/core";
2
+ import { parseString, splitCookiesString } from "set-cookie-parser";
3
+ import { serialize } from "cookie";
4
+ const actions = [
5
+ "providers",
6
+ "session",
7
+ "csrf",
8
+ "signin",
9
+ "signout",
10
+ "callback",
11
+ "verify-request",
12
+ "error"
13
+ ];
14
+ const getSetCookieCallback = (cook) => {
15
+ if (!cook)
16
+ return;
17
+ const splitCookie = splitCookiesString(cook);
18
+ for (const cookName of [
19
+ "__Secure-next-auth.session-token",
20
+ "next-auth.session-token",
21
+ "next-auth.pkce.code_verifier",
22
+ "__Secure-next-auth.pkce.code_verifier"
23
+ ]) {
24
+ const temp = splitCookie.find((e) => e.startsWith(`${cookName}=`));
25
+ if (temp) {
26
+ return parseString(temp);
27
+ }
28
+ }
29
+ return parseString(splitCookie?.[0] ?? "");
30
+ };
31
+ function SolidAuthHandler(prefix, authOptions) {
32
+ return async (event) => {
33
+ const { request } = event;
34
+ const url = new URL(request.url);
35
+ const action = url.pathname.slice(prefix.length + 1).split("/")[0];
36
+ if (!actions.includes(action) || !url.pathname.startsWith(prefix + "/")) {
37
+ return;
38
+ }
39
+ const res = await Auth(request, authOptions);
40
+ if (["callback", "signin", "signout"].includes(action)) {
41
+ const parsedCookie = getSetCookieCallback(
42
+ res.clone().headers.get("Set-Cookie")
43
+ );
44
+ if (parsedCookie) {
45
+ res.headers.set(
46
+ "Set-Cookie",
47
+ serialize(parsedCookie.name, parsedCookie.value, parsedCookie)
48
+ );
49
+ }
50
+ }
51
+ return res;
52
+ };
53
+ }
54
+ function SolidAuth(config) {
55
+ const { prefix = "/api/auth", ...authOptions } = config;
56
+ authOptions.secret ??= process.env.AUTH_SECRET;
57
+ authOptions.trustHost ??= !!(process.env.AUTH_TRUST_HOST ?? process.env.VERCEL ?? process.env.NODE_ENV !== "production");
58
+ const handler = SolidAuthHandler(prefix, authOptions);
59
+ return {
60
+ async GET(event) {
61
+ return await handler(event);
62
+ },
63
+ async POST(event) {
64
+ return await handler(event);
65
+ }
66
+ };
67
+ }
68
+ async function getSession(req, options) {
69
+ options.secret ??= process.env.AUTH_SECRET;
70
+ options.trustHost ??= true;
71
+ const url = new URL("/api/auth/session", req.url);
72
+ const response = await Auth(
73
+ new Request(url, { headers: req.headers }),
74
+ options
75
+ );
76
+ const { status = 200 } = response;
77
+ const data = await response.json();
78
+ if (!data || !Object.keys(data).length)
79
+ return null;
80
+ if (status === 200)
81
+ return data;
82
+ throw new Error(data.message);
83
+ }
84
+ export {
85
+ SolidAuth,
86
+ getSession
87
+ };
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@auth/solid-start",
3
+ "description": "Authentication for SolidStart.",
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "files": [
7
+ "client.*",
8
+ "index.*",
9
+ "src"
10
+ ],
11
+ "exports": {
12
+ ".": {
13
+ "types": "./index.d.ts",
14
+ "import": "./index.js"
15
+ },
16
+ "./client": {
17
+ "types": "./client.d.ts",
18
+ "import": "./client.js"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "devDependencies": {
26
+ "@solidjs/meta": "^0.28.0",
27
+ "@types/cookie": "0.5.1",
28
+ "@types/node": "^18.7.14",
29
+ "@types/set-cookie-parser": "^2.4.2",
30
+ "solid-js": "^1.5.7",
31
+ "solid-start": "^0.2.1",
32
+ "tsup": "^6.5.0",
33
+ "typescript": "^4.8.2",
34
+ "@auth/core": "0.2.3",
35
+ "next-auth": "4.18.6"
36
+ },
37
+ "peerDependencies": {
38
+ "@auth/core": "~0.2.2 || ^0.2.2",
39
+ "solid-js": "^1.5.7",
40
+ "solid-start": "^0.2.1"
41
+ },
42
+ "dependencies": {
43
+ "set-cookie-parser": "^2.5.1"
44
+ },
45
+ "keywords": [
46
+ "SolidJS",
47
+ "SolidStart",
48
+ "Auth"
49
+ ],
50
+ "author": "OrJDev <orjdeveloper@gmail.com>",
51
+ "repository": "https://github.com/nextauthjs/next-auth",
52
+ "license": "ISC",
53
+ "scripts": {
54
+ "build": "tsup --config ./tsup.config.js && node scripts/postbuild",
55
+ "patch": "npm version patch --no-git-tag-version",
56
+ "clean": "rm -rf client.* index.*"
57
+ }
58
+ }
package/src/client.ts ADDED
@@ -0,0 +1,102 @@
1
+ import type {
2
+ LiteralUnion,
3
+ SignInOptions,
4
+ SignInAuthorizationParams,
5
+ SignOutParams,
6
+ } from "next-auth/react"
7
+ import type {
8
+ BuiltInProviderType,
9
+ RedirectableProviderType,
10
+ } from "@auth/core/providers"
11
+
12
+ /**
13
+ * Client-side method to initiate a signin flow
14
+ * or send the user to the signin page listing all possible providers.
15
+ * Automatically adds the CSRF token to the request.
16
+ *
17
+ * [Documentation](https://next-auth.js.org/getting-started/client#signin)
18
+ */
19
+ export async function signIn<
20
+ P extends RedirectableProviderType | undefined = undefined
21
+ >(
22
+ providerId?: LiteralUnion<
23
+ P extends RedirectableProviderType
24
+ ? P | BuiltInProviderType
25
+ : BuiltInProviderType
26
+ >,
27
+ options?: SignInOptions,
28
+ authorizationParams?: SignInAuthorizationParams
29
+ ) {
30
+ const { callbackUrl = window.location.href, redirect = true } = options ?? {}
31
+
32
+ // TODO: Support custom providers
33
+ const isCredentials = providerId === "credentials"
34
+ const isEmail = providerId === "email"
35
+ const isSupportingReturn = isCredentials || isEmail
36
+
37
+ // TODO: Handle custom base path
38
+ const signInUrl = `/api/auth/${
39
+ isCredentials ? "callback" : "signin"
40
+ }/${providerId}`
41
+
42
+ const _signInUrl = `${signInUrl}?${new URLSearchParams(authorizationParams)}`
43
+
44
+ // TODO: Handle custom base path
45
+ const csrfTokenResponse = await fetch("/api/auth/csrf")
46
+ const { csrfToken } = await csrfTokenResponse.json()
47
+
48
+ const res = await fetch(_signInUrl, {
49
+ method: "post",
50
+ headers: {
51
+ "Content-Type": "application/x-www-form-urlencoded",
52
+ "X-Auth-Return-Redirect": "1",
53
+ },
54
+ // @ts-expect-error -- ignore
55
+ body: new URLSearchParams({
56
+ ...options,
57
+ csrfToken,
58
+ callbackUrl,
59
+ }),
60
+ })
61
+
62
+ const data = await res.clone().json()
63
+ const error = new URL(data.url).searchParams.get("error")
64
+ if (redirect || !isSupportingReturn || !error) {
65
+ // TODO: Do not redirect for Credentials and Email providers by default in next major
66
+ window.location.href = data.url ?? data.redirect ?? callbackUrl
67
+ // If url contains a hash, the browser does not reload the page. We reload manually
68
+ if (data.url.includes("#")) window.location.reload()
69
+ return
70
+ }
71
+ return res
72
+ }
73
+
74
+ /**
75
+ * Signs the user out, by removing the session cookie.
76
+ * Automatically adds the CSRF token to the request.
77
+ *
78
+ * [Documentation](https://next-auth.js.org/getting-started/client#signout)
79
+ */
80
+ export async function signOut(options?: SignOutParams) {
81
+ const { callbackUrl = window.location.href } = options ?? {}
82
+ // TODO: Custom base path
83
+ const csrfTokenResponse = await fetch("/api/auth/csrf")
84
+ const { csrfToken } = await csrfTokenResponse.json()
85
+ const res = await fetch(`/api/auth/signout`, {
86
+ method: "post",
87
+ headers: {
88
+ "Content-Type": "application/x-www-form-urlencoded",
89
+ "X-Auth-Return-Redirect": "1",
90
+ },
91
+ body: new URLSearchParams({
92
+ csrfToken,
93
+ callbackUrl,
94
+ }),
95
+ })
96
+ const data = await res.json()
97
+
98
+ const url = data.url ?? data.redirect ?? callbackUrl
99
+ window.location.href = url
100
+ // If url contains a hash, the browser does not reload the page. We reload manually
101
+ if (url.includes("#")) window.location.reload()
102
+ }
package/src/index.ts ADDED
@@ -0,0 +1,114 @@
1
+ import { Auth } from "@auth/core"
2
+ import { Cookie, parseString, splitCookiesString } from "set-cookie-parser"
3
+ import { serialize } from "cookie"
4
+ import type { AuthAction, AuthConfig, Session } from "@auth/core/types"
5
+
6
+ export interface SolidAuthConfig extends AuthConfig {
7
+ /**
8
+ * Defines the base path for the auth routes.
9
+ * @default '/api/auth'
10
+ */
11
+ prefix?: string
12
+ }
13
+
14
+ const actions: AuthAction[] = [
15
+ "providers",
16
+ "session",
17
+ "csrf",
18
+ "signin",
19
+ "signout",
20
+ "callback",
21
+ "verify-request",
22
+ "error",
23
+ ]
24
+
25
+ // currently multiple cookies are not supported, so we keep the next-auth.pkce.code_verifier cookie for now:
26
+ // because it gets updated anyways
27
+ // src: https://github.com/solidjs/solid-start/issues/293
28
+ const getSetCookieCallback = (cook?: string | null): Cookie | undefined => {
29
+ if (!cook) return
30
+ const splitCookie = splitCookiesString(cook)
31
+ for (const cookName of [
32
+ "__Secure-next-auth.session-token",
33
+ "next-auth.session-token",
34
+ "next-auth.pkce.code_verifier",
35
+ "__Secure-next-auth.pkce.code_verifier",
36
+ ]) {
37
+ const temp = splitCookie.find((e) => e.startsWith(`${cookName}=`))
38
+ if (temp) {
39
+ return parseString(temp)
40
+ }
41
+ }
42
+ return parseString(splitCookie?.[0] ?? "") // just return the first cookie if no session token is found
43
+ }
44
+
45
+ function SolidAuthHandler(prefix: string, authOptions: SolidAuthConfig) {
46
+ return async (event: any) => {
47
+ const { request } = event
48
+ const url = new URL(request.url)
49
+ const action = url.pathname
50
+ .slice(prefix.length + 1)
51
+ .split("/")[0] as AuthAction
52
+
53
+ if (!actions.includes(action) || !url.pathname.startsWith(prefix + "/")) {
54
+ return
55
+ }
56
+
57
+ const res = await Auth(request, authOptions)
58
+ if (["callback", "signin", "signout"].includes(action)) {
59
+ const parsedCookie = getSetCookieCallback(
60
+ res.clone().headers.get("Set-Cookie")
61
+ )
62
+ if (parsedCookie) {
63
+ res.headers.set(
64
+ "Set-Cookie",
65
+ serialize(parsedCookie.name, parsedCookie.value, parsedCookie as any)
66
+ )
67
+ }
68
+ }
69
+ return res
70
+ }
71
+ }
72
+
73
+ export function SolidAuth(config: SolidAuthConfig) {
74
+ const { prefix = "/api/auth", ...authOptions } = config
75
+ authOptions.secret ??= process.env.AUTH_SECRET
76
+ authOptions.trustHost ??= !!(
77
+ process.env.AUTH_TRUST_HOST ??
78
+ process.env.VERCEL ??
79
+ process.env.NODE_ENV !== "production"
80
+ )
81
+ const handler = SolidAuthHandler(prefix, authOptions)
82
+ return {
83
+ async GET(event: any) {
84
+ return await handler(event)
85
+ },
86
+ async POST(event: any) {
87
+ return await handler(event)
88
+ },
89
+ }
90
+ }
91
+
92
+ export type GetSessionResult = Promise<Session | null>
93
+
94
+ export async function getSession(
95
+ req: Request,
96
+ options: AuthConfig
97
+ ): GetSessionResult {
98
+ options.secret ??= process.env.AUTH_SECRET
99
+ options.trustHost ??= true
100
+
101
+ const url = new URL("/api/auth/session", req.url)
102
+ const response = await Auth(
103
+ new Request(url, { headers: req.headers }),
104
+ options
105
+ )
106
+
107
+ const { status = 200 } = response
108
+
109
+ const data = await response.json()
110
+
111
+ if (!data || !Object.keys(data).length) return null
112
+ if (status === 200) return data
113
+ throw new Error(data.message)
114
+ }