auth 1.2.3 → 1.5.0-beta.16

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.
@@ -1,103 +0,0 @@
1
- import fs from "node:fs"
2
- import path from "node:path"
3
- import * as y from "yoctocolors"
4
-
5
- /**
6
- * Generates an Apple client secret.
7
- *
8
- * @param {object} options
9
- * @param {string} options.teamId - Apple Team ID.
10
- * @param {string} options.clientId - Apple Client ID.
11
- * @param {string} options.keyId - Apple Key ID.
12
- * @param {string} options.privateKey - Apple Private Key.
13
- * @param {number} options.expiresInDays - Days until the secret expires.
14
- *
15
- * @see https://developer.apple.com/documentation/accountorganizationaldatasharing/creating-a-client-secret
16
- */
17
- export async function appleGenSecret({
18
- teamId: iss,
19
- clientId: sub,
20
- keyId: kid,
21
- privateKey,
22
- expiresInDays,
23
- }) {
24
- const expiresIn = 86400 * expiresInDays
25
- const exp = Math.ceil(Date.now() / 1000) + expiresIn
26
-
27
- const secret = await signJWT(sub, iss, kid, privateKey, exp)
28
-
29
- console.log(
30
- y.green(
31
- `Apple client secret generated. Valid until: ${new Date(exp * 1000)}`
32
- )
33
- )
34
-
35
- return secret
36
- }
37
-
38
- /**
39
- *
40
- * @param {string} sub - Apple client ID.
41
- * @param {string} iss - Apple team ID.
42
- * @param {string} kid - Apple key ID.
43
- * @param {string} privateKeyPath - Apple private key.
44
- * @param {Date} exp - Expiry date.
45
- */
46
- async function signJWT(sub, iss, kid, privateKeyPath, exp) {
47
- const header = { alg: "ES256", kid }
48
-
49
- const payload = {
50
- iss,
51
- iat: Date.now() / 1000,
52
- exp,
53
- aud: "https://appleid.apple.com",
54
- sub,
55
- }
56
-
57
- const parts = [
58
- toBase64Url(encoder.encode(JSON.stringify(header))),
59
- toBase64Url(encoder.encode(JSON.stringify(payload))),
60
- ]
61
-
62
- const privateKey = fs
63
- .readFileSync(path.resolve(privateKeyPath), "utf8")
64
- .replace(/-----BEGIN PRIVATE KEY-----|\n|-----END PRIVATE KEY-----/g, "")
65
-
66
- const signature = await sign(parts.join("."), privateKey)
67
-
68
- parts.push(toBase64Url(signature))
69
- return parts.join(".")
70
- }
71
-
72
- const encoder = new TextEncoder()
73
- function toBase64Url(data) {
74
- return btoa(String.fromCharCode(...new Uint8Array(data)))
75
- .replace(/\+/g, "-")
76
- .replace(/\//g, "_")
77
- .replace(/=+$/, "")
78
- }
79
-
80
- async function sign(data, private_key) {
81
- const pem = private_key.replace(
82
- /-----BEGIN PRIVATE KEY-----|\n|-----END PRIVATE KEY-----/g,
83
- ""
84
- )
85
- const binaryDerString = atob(pem)
86
- const binaryDer = new Uint8Array(
87
- [...binaryDerString].map((char) => char.charCodeAt(0))
88
- )
89
-
90
- const privateKey = await globalThis.crypto.subtle.importKey(
91
- "pkcs8",
92
- binaryDer.buffer,
93
- { name: "ECDSA", namedCurve: "P-256" },
94
- true,
95
- ["sign"]
96
- )
97
-
98
- return await globalThis.crypto.subtle.sign(
99
- { name: "ECDSA", hash: { name: "SHA-256" } },
100
- privateKey,
101
- encoder.encode(data)
102
- )
103
- }
package/lib/detect.js DELETED
@@ -1,58 +0,0 @@
1
- // @ts-check
2
- import { readFile } from "node:fs/promises"
3
- import { join } from "node:path"
4
- import { frameworks } from "../lib/meta.js"
5
- import * as y from "yoctocolors"
6
-
7
- /**
8
- * When this function runs in a framework directory we support,
9
- * it will return the framework's name
10
- * @param {string} path
11
- * @returns {Promise<import("./meta").SupportedFramework | "unknown">}
12
- */
13
- export async function detectFramework(path = "") {
14
- const dir = process.cwd()
15
- const packageJsonPath = join(dir, path, "package.json")
16
- try {
17
- const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8"))
18
-
19
- /** @type {import("./meta").SupportedFramework[]} */
20
- const foundFrameworks = []
21
-
22
- if (packageJson?.dependencies?.["next"]) foundFrameworks.push("next")
23
- if (packageJson?.dependencies?.["express"]) foundFrameworks.push("express")
24
- if (packageJson?.devDependencies?.["@sveltejs/kit"])
25
- foundFrameworks.push("sveltekit")
26
-
27
- if (foundFrameworks.length === 1) return foundFrameworks[0]
28
-
29
- if (foundFrameworks.length > 1) {
30
- console.error(
31
- `Multiple supported frameworks detected: ${foundFrameworks.join(", ")}`
32
- )
33
- return "unknown"
34
- }
35
- return "unknown"
36
- } catch (error) {
37
- console.error(error)
38
- return "unknown"
39
- }
40
- }
41
-
42
- export async function requireFramework(path = "") {
43
- const framework = await detectFramework(path)
44
-
45
- if (framework === "unknown") {
46
- console.error(
47
- y.red(
48
- `No framework detected. Currently supported frameworks are: ${y.bold(
49
- Object.keys(frameworks).join(", ")
50
- )}`
51
- )
52
- )
53
-
54
- process.exit(0)
55
- }
56
-
57
- return framework
58
- }
package/lib/is-online.js DELETED
@@ -1,46 +0,0 @@
1
- // @ts-check
2
-
3
- import { execSync } from "node:child_process"
4
- import dns from "node:dns/promises"
5
-
6
- function getProxy() {
7
- if (process.env.https_proxy) {
8
- return process.env.https_proxy
9
- }
10
-
11
- try {
12
- const httpsProxy = execSync("npm config get https-proxy").toString().trim()
13
- return httpsProxy !== "null" ? httpsProxy : undefined
14
- } catch (e) {
15
- return
16
- }
17
- }
18
-
19
- export async function getOnline() {
20
- try {
21
- await dns.lookup("registry.yarnpkg.com")
22
- // If DNS lookup succeeds, we are online
23
- return true
24
- } catch {
25
- // The DNS lookup failed, but we are still fine as long as a proxy has been set
26
- const proxy = getProxy()
27
- if (!proxy) {
28
- return false
29
- }
30
-
31
- const { hostname } = new URL(proxy)
32
- if (!hostname) {
33
- // Invalid proxy URL
34
- return false
35
- }
36
-
37
- try {
38
- await dns.lookup(hostname)
39
- // If DNS lookup succeeds for the proxy server, we are online
40
- return true
41
- } catch {
42
- // The DNS lookup for the proxy server also failed, so we are offline
43
- return false
44
- }
45
- }
46
- }
package/lib/markdown.js DELETED
@@ -1,59 +0,0 @@
1
- // @ts-check
2
- import * as y from "yoctocolors"
3
-
4
- // double char markdown matchers
5
- const BOLD_REGEX = /\*{2}([^*]+)\*{2}/g
6
- const UNDERLINE_REGEX = /_{2}([^_]+)_{2}/g
7
- const STRIKETHROUGH_REGEX = /~{2}([^~]+)~{2}/g
8
- const LINK_REGEX = /\[([^\]]+)\]\(([^)]+)\)/g
9
-
10
- // single char markdown matchers
11
- const ITALIC_REGEX = /(?<!\\)\*(.+)(?<!\\)\*|(?<!\\)_(.+)(?<!\\)_/g
12
-
13
- export function link(text, url) {
14
- return `\x1b]8;;${url}\x1b\\${text}\x1b]8;;\x1b\\`
15
- }
16
-
17
- /**
18
- * @param {string} input
19
- * @returns {string}
20
- */
21
- export function markdownToAnsi(input) {
22
- input = input.replace(BOLD_REGEX, (...args) => y.bold(args[1]))
23
- input = input.replace(UNDERLINE_REGEX, (...args) => y.underline(args[1]))
24
- input = input.replace(STRIKETHROUGH_REGEX, (...args) =>
25
- y.strikethrough(args[1])
26
- )
27
- input = input.replace(ITALIC_REGEX, (...args) => y.italic(args[1] || args[2]))
28
- input = input.replace(/(?<!\\)\\/g, "")
29
-
30
- // @ts-expect-error
31
- input = input.replaceAll(LINK_REGEX, (...args) =>
32
- y.blue(" " + link(args[2], args[1]))
33
- )
34
- return input
35
- }
36
-
37
- /**
38
- * @param {string} str
39
- * @param {number} maxLineLength
40
- * @returns {string}
41
- */
42
- export function breakStringToLines(str, maxLineLength) {
43
- let result = ""
44
- let line = ""
45
-
46
- str.split(" ").forEach((word) => {
47
- if (line.length + word.length + 1 > maxLineLength) {
48
- result += line + "\n"
49
- line = word
50
- } else {
51
- if (line) line += " "
52
- line += word
53
- }
54
- })
55
-
56
- if (line) result += line
57
-
58
- return result
59
- }
package/lib/meta.js DELETED
@@ -1,217 +0,0 @@
1
- // @ts-check
2
-
3
- // TODO: Get these programmatically
4
-
5
- /**
6
- * @typedef {"next" | "express" | "sveltekit"} SupportedFramework
7
- */
8
-
9
- export const frameworks = {
10
- next: {
11
- name: "Next.js",
12
- src: "https://github.com/nextauthjs/next-auth-example",
13
- demo: "https://next-auth-example.vercel.app",
14
- path: "/api/auth",
15
- port: 3000,
16
- envFile: ".env.local",
17
- },
18
- sveltekit: {
19
- name: "SvelteKit",
20
- src: "https://github.com/nextauthjs/sveltekit-auth-example",
21
- demo: "https://sveltekit-auth-example.vercel.app",
22
- path: "/auth",
23
- port: 5173,
24
- envFile: ".env",
25
- },
26
- express: {
27
- name: "Express",
28
- src: "https://github.com/nextauthjs/express-auth-example",
29
- demo: "https://express-auth-example.vercel.app",
30
- path: "/auth",
31
- port: 3000,
32
- envFile: ".env",
33
- },
34
- }
35
-
36
- export const providers = {
37
- "42-school": { name: "42 School", setupUrl: undefined },
38
- apple: {
39
- name: "Apple",
40
- setupUrl:
41
- "https://developer.apple.com/account/resources/identifiers/list/serviceId",
42
- },
43
- asgardeo: { name: "Asgardeo", setupUrl: undefined },
44
- auth0: { name: "Auth0", setupUrl: undefined },
45
- authentik: { name: "Authentik", setupUrl: undefined },
46
- "azure-ad-b2c": { name: "Azure AD B2C", setupUrl: undefined },
47
- "azure-ad": { name: "Azure AD", setupUrl: undefined },
48
- "azure-devops": { name: "Azure DevOps", setupUrl: undefined },
49
- battlenet: { name: "Battlenet", setupUrl: undefined },
50
- beyondidentity: { name: "Beyond Identity", setupUrl: undefined },
51
- box: { name: "Box", setupUrl: undefined },
52
- "boxyhq-saml": { name: "Boxyhq SAML", setupUrl: undefined },
53
- bungie: { name: "Bungie", setupUrl: undefined },
54
- "click-up": { name: "Click-up", setupUrl: undefined },
55
- cognito: { name: "Cognito", setupUrl: undefined },
56
- coinbase: { name: "Coinbase", setupUrl: undefined },
57
- credentials: { name: "Credentials", setupUrl: undefined },
58
- descope: { name: "Descope", setupUrl: undefined },
59
- discord: {
60
- name: "Discord",
61
- setupUrl: "https://discord.com/developers/applications",
62
- instructions: `\
63
- 1. Click on *New Application*
64
- 2. Set *Application name* (can be anything)
65
- 3. Click on *OAuth2* in the side menu
66
- 4. Click on *Add Redirect* and paste the callback URI (on your clipboard)
67
- 5. Click on *Save Changes*
68
- 6. Copy and paste the *Client ID*
69
- 7. Copy and paste the *Client Secret*
70
- `,
71
- },
72
- dribbble: { name: "Dribbble", setupUrl: undefined },
73
- dropbox: { name: "Dropbox", setupUrl: undefined },
74
- "duende-identity-server6": {
75
- name: "Duende Identity Server 6",
76
- setupUrl: undefined,
77
- },
78
- email: { name: "Email", setupUrl: undefined },
79
- eveonline: { name: "Eveonline", setupUrl: undefined },
80
- facebook: { name: "Facebook", setupUrl: undefined },
81
- faceit: { name: "Faceit", setupUrl: undefined },
82
- foursquare: { name: "Foursquare", setupUrl: undefined },
83
- freshbooks: { name: "Freshbooks", setupUrl: undefined },
84
- fusionauth: { name: "FusionAuth", setupUrl: undefined },
85
- github: {
86
- name: "GitHub",
87
- setupUrl: "https://github.com/settings/applications/new",
88
- instructions: `\
89
- 1. Set *Application name* (can be anything)
90
- 2. Set *Homepage URL* (your business/website, but can be anything)
91
- 3. Paste the redirect URI (on your clipboard) to *Authorization callback URL*
92
- 4. Click *Register application*
93
- 5. Paste the *Client ID* back here
94
- 6. Click *Generate a new client secret*
95
- 7. Paste the *Client secret* back here (Note: This is the only time you can see it)`,
96
- },
97
- gitlab: { name: "GitLab", setupUrl: undefined },
98
- google: {
99
- name: "Google",
100
- setupUrl: "https://console.cloud.google.com/apis/credentials/oauthclient",
101
- instructions: `\
102
- 1. Choose *Application Type: Web Application*
103
- 2. Paste the redirect URI (on your clipboard) to *Authorized redirect URIs*
104
- 3. Fill out the rest of the form
105
- 4. Click *Create*`,
106
- },
107
- hubspot: { name: "Hubspot", setupUrl: undefined },
108
- "identity-server4": { name: "Identity Server 4", setupUrl: undefined },
109
- instagram: { name: "Instagram", setupUrl: undefined },
110
- kakao: { name: "Kakao", setupUrl: undefined },
111
- keycloak: { name: "Keycloak", setupUrl: undefined },
112
- line: { name: "Line", setupUrl: undefined },
113
- linkedin: {
114
- name: "LinkedIn",
115
- setupUrl: "https://linkedin.com/developers/apps",
116
- instructions: `\
117
- 1. Click on *Create app*
118
- 2. Set *App name* (can be anything)
119
- 3. Create a *LinkedIn page*
120
- 4. Set *LinkedIn Page URL* (LinkedIn page you created)
121
- 5. Set *App logo* (can be anything)
122
- 6. Navigate to *Auth* in the top menu
123
- 7. Set *Authorized redirect URLs for your app* (paste the callback URI on your clipboard)
124
- 8. Copy and paste the *Client ID*
125
- 9. Copy and paste the *Primary Client Secret*
126
- `,
127
- },
128
- mailchimp: { name: "Mailchimp", setupUrl: undefined },
129
- mailru: { name: "Mail.ru", setupUrl: undefined },
130
- mastodon: { name: "Mastodon", setupUrl: undefined },
131
- mattermost: { name: "Mattermost", setupUrl: undefined },
132
- medium: { name: "Medium", setupUrl: undefined },
133
- "microsoft-entra-id": { name: "Microsoft Entra ID", setupUrl: undefined },
134
- naver: { name: "Naver", setupUrl: undefined },
135
- netlify: { name: "Netlify", setupUrl: undefined },
136
- netsuite: { name: "Netsuite", setupUrl: undefined },
137
- nodemailer: { name: "Nodemailer", setupUrl: undefined },
138
- notion: { name: "Notion", setupUrl: undefined },
139
- okta: { name: "Okta", setupUrl: undefined },
140
- onelogin: { name: "Onelogin", setupUrl: undefined },
141
- "ory-hydra": { name: "Ory Hydra", setupUrl: undefined },
142
- osso: { name: "Osso", setupUrl: undefined },
143
- osu: { name: "Osu", setupUrl: undefined },
144
- passage: { name: "Passage", setupUrl: undefined },
145
- passkey: { name: "Passkey", setupUrl: undefined },
146
- patreon: { name: "Patreon", setupUrl: undefined },
147
- pinterest: { name: "Pinterest", setupUrl: undefined },
148
- pipedrive: { name: "Pipedrive", setupUrl: undefined },
149
- postmark: { name: "Postmark", setupUrl: undefined },
150
- reddit: { name: "Reddit", setupUrl: undefined },
151
- resend: { name: "Resend", setupUrl: undefined },
152
- salesforce: { name: "Salesforce", setupUrl: undefined },
153
- sendgrid: { name: "Sendgrid", setupUrl: undefined },
154
- slack: { name: "Slack", setupUrl: undefined },
155
- spotify: { name: "Spotify", setupUrl: undefined },
156
- strava: { name: "Strava", setupUrl: undefined },
157
- tiktok: { name: "Tiktok", setupUrl: undefined },
158
- todoist: { name: "Todoist", setupUrl: undefined },
159
- trakt: { name: "Trakt", setupUrl: undefined },
160
- twitch: { name: "Twitch", setupUrl: undefined },
161
- twitter: {
162
- name: "Twitter",
163
- setupUrl: "https://developer.x.com/en/portal/dashboard",
164
- instructions: `\
165
- 1. Create a project
166
- 2. Set the *Project name* (can be anything)
167
- 3. Choose a use case, then click Next
168
- 4. Give it a description, then click Next
169
- 5. An app will be created for you
170
- 6. Set the app name
171
- 7. Click on *App Settings*
172
- 8. Click *Set up* under *User authentication settings*
173
- 9. Select *Web App, Automated App or Bot* at "Type of app"
174
- 10. Add the callback URI (on your clipboard) to *Callback URLs*
175
- 11. Fill out the other required fields (your website)
176
- 12. Copy and paste the *Client ID*
177
- 13. Copy and paste the *Client Secret*
178
- 14. Click *Done*`,
179
- },
180
- "united-effects": { name: "United Effects", setupUrl: undefined },
181
- vk: { name: "Vk", setupUrl: undefined },
182
- webex: { name: "Webex", setupUrl: undefined },
183
- wikimedia: { name: "Wikimedia", setupUrl: undefined },
184
- wordpress: { name: "Wordpress", setupUrl: undefined },
185
- workos: { name: "WorkOS", setupUrl: undefined },
186
- yandex: { name: "Yandex", setupUrl: undefined },
187
- zitadel: { name: "Zitadel", setupUrl: undefined },
188
- zoho: { name: "Zoho", setupUrl: undefined },
189
- zoom: { name: "Zoom", setupUrl: undefined },
190
- }
191
-
192
- export const adapters = {
193
- none: "None",
194
- "adapter-azure-tables": "Azure Tables",
195
- d1: "d1",
196
- dgraph: "Dgraph",
197
- drizzle: "Drizzle",
198
- dynamodb: "DynamoDB",
199
- edgedb: "EdgeDB",
200
- fauna: "Fauna",
201
- firebase: "Firebase",
202
- hasura: "Hasura",
203
- kysely: "Kysely",
204
- "mikro-orm": "MikroORM",
205
- mongodb: "MongoDB",
206
- neo4j: "Neo4j",
207
- pg: "PostgreSQL",
208
- pouchdb: "PouchDB",
209
- prisma: "Prisma",
210
- sequelize: "Sequelize",
211
- supabase: "Supabase",
212
- surrealdb: "SurrealDB",
213
- typeorm: "TypeORM",
214
- unstorage: "Unstorage",
215
- "upstash-redis": "Upstash Redis",
216
- xata: "Xata",
217
- }
@@ -1,70 +0,0 @@
1
- // @ts-check
2
-
3
- import { yellow } from "yoctocolors"
4
- import { spawn } from "node:child_process"
5
- import { getOnline } from "./is-online.js"
6
-
7
- /**
8
- * @typedef {'npm' | 'pnpm' | 'yarn' | 'bun'} PackageManager
9
- */
10
-
11
- /**
12
- * @source https://github.com/vercel/next.js/blob/canary/packages/create-next-app/helpers/get-pkg-manager.ts
13
- * @returns {PackageManager}
14
- */
15
- export function getPkgManager() {
16
- const userAgent = process.env.npm_config_user_agent || ""
17
-
18
- if (userAgent.startsWith("pnpm")) {
19
- return "pnpm"
20
- }
21
-
22
- if (userAgent.startsWith("bun")) {
23
- return "bun"
24
- }
25
-
26
- if (userAgent.startsWith("yarn")) {
27
- return "yarn"
28
- }
29
-
30
- return "npm"
31
- }
32
-
33
- /**
34
- * @source https://github.com/vercel/next.js/blob/canary/packages/create-next-app/helpers/install.ts
35
- * Spawn a package manager installation based on user preference.
36
- *
37
- * @returns A Promise that resolves once the installation is finished.
38
- */
39
- export async function install() {
40
- const packageManager = getPkgManager()
41
- /** @type {string[]} */
42
- const args = ["install"]
43
- if (!(await getOnline())) {
44
- console.log(
45
- yellow("You appear to be offline.\nFalling back to the local cache.")
46
- )
47
- args.push("--offline")
48
- }
49
- return new Promise((resolve, reject) => {
50
- /** Spawn the installation process. */
51
- const child = spawn(packageManager, args, {
52
- stdio: "inherit",
53
- env: {
54
- ...process.env,
55
- ADBLOCK: "1",
56
- // we set NODE_ENV to development as pnpm skips dev
57
- // dependencies when production
58
- NODE_ENV: "development",
59
- DISABLE_OPENCOLLECTIVE: "1",
60
- },
61
- })
62
- child.on("close", (code) => {
63
- if (code !== 0) {
64
- reject({ command: `${packageManager} ${args.join(" ")}` })
65
- return
66
- }
67
- resolve(void 0)
68
- })
69
- })
70
- }
package/lib/write-env.js DELETED
@@ -1,61 +0,0 @@
1
- // @ts-check
2
-
3
- import * as y from "yoctocolors"
4
- import { readFile, writeFile } from "node:fs/promises"
5
- import prompt from "prompts"
6
- import { join } from "node:path"
7
- import { frameworks } from "./meta.js"
8
- import { detectFramework } from "./detect.js"
9
-
10
- /**
11
- * Add/update key-value pair(s) to a .env file
12
- * @param {Record<string, string>} env
13
- * @param {string|undefined} envPath
14
- * @param {boolean} comment
15
- */
16
- export async function updateEnvFile(env, envPath = "", comment = true) {
17
- const framework = await detectFramework(envPath)
18
- const dotEnvFile = frameworks[framework]?.envFile
19
- const file = join(process.cwd(), envPath, dotEnvFile)
20
- let content = ""
21
- let read = false
22
- let created = false
23
- for (const [key, value] of Object.entries(env)) {
24
- const line = `${key}="${value}"${
25
- comment ? " # Added by `npx auth`. Read more: https://cli.authjs.dev" : ""
26
- }`
27
- try {
28
- if (!read) {
29
- content = await readFile(file, "utf-8")
30
- read = true
31
- }
32
- if (!content.includes(`${key}=`)) {
33
- console.log(`➕ Added \`${key}\` to ${y.italic(file)}.`)
34
- content = content ? `${content}\n${line}` : line
35
- } else {
36
- const { overwrite } = await prompt({
37
- type: "confirm",
38
- name: "overwrite",
39
- message: `Overwrite existing \`${key}\`?`,
40
- initial: false,
41
- })
42
- if (!overwrite) continue
43
- console.log(`✨ Updated \`${key}\` in ${y.italic(file)}.`)
44
- content = content.replace(new RegExp(`${key}=(.*)`), `${line}`)
45
- }
46
- } catch (error) {
47
- if (error.code === "ENOENT") {
48
- if (!created) {
49
- console.log(`📝 Created ${y.italic(file)} with \`${key}\`.`)
50
- created = true
51
- } else {
52
- console.log(`➕ Added \`${key}\` to ${y.italic(file)}.`)
53
- }
54
- content = content ? `${content}\n${line}` : line
55
- } else {
56
- throw error
57
- }
58
- }
59
- }
60
- if (content) await writeFile(file, content)
61
- }