Compare commits

...

80 Commits

Author SHA1 Message Date
dnikomon
4dcdb62dca fix: remove nextauth from authorization params (#3332)
Co-authored-by: Balázs Orbán <info@balazsorban.com>
2021-12-02 19:43:42 +01:00
Nico Domino
1f4b7d8089 chore: add opencollective to package.json (#3333) 2021-12-02 12:50:14 +01:00
Balázs Orbán
fedb84872d docs: add top contributors to package.json 2021-12-01 16:44:20 +01:00
Balázs Orbán
c0dddfb77f docs: upgrade README 2021-12-01 16:40:21 +01:00
Balázs Orbán
50fe115df6 Release v4 2021-12-01 16:32:35 +01:00
Jameel Khan
cc17ddf8aa fix: Fallback to --color-text when no color-brand (#3313) 2021-12-01 15:01:11 +01:00
Balázs Orbán
8644e553ed Merge branch 'main' into beta 2021-11-30 19:20:56 +01:00
Nisala Kalupahana
d1d0db43ea feat(providers): ensure GitHub provider always gives an email (#3302)
* Ensure that GitHub provider always gives an email

* Update src/providers/github.js

Co-authored-by: Balázs Orbán <info@balazsorban.com>

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2021-11-29 23:58:49 +01:00
Balázs Orbán
b01f6805d3 chore(providers): TS improvements (#3295) 2021-11-28 17:52:56 +01:00
Balázs Orbán
c44b860b9e feat(providers): refactor Apple provider (#2875)
* chore: remove legacy code

* fix(providers): refactor Apple provider

* chore(dev): add Apple provider

* docs(providers): add `generateClientSecret` to JSDoc

* fix(providers): use `jose@4`

* fix(providers): use seconds since epoch, correct sign

* chore(providers): move secret generator into a script
2021-11-28 17:52:24 +01:00
Khánh Hoàng
22f74d7c4d fix(providers): correct authorization url for Atlassian (#2999)
* fix(provider): correct authorization for Atlassian

* feat(providers): use wellKnown for better configuration

* fix(atlassian): switch back to raw config

* fix(providers): pass generic to `OAuthUserConfig`

Co-authored-by: Lluis Agusti <hi@llu.lu>
Co-authored-by: Balázs Orbán <info@balazsorban.com>
2021-11-28 15:49:24 +01:00
Balázs Orbán
2570168660 fix: add custom error message when session required (#3288) 2021-11-28 15:38:02 +01:00
Balázs Orbán
187a1474f5 feat(oauth): expose httpOptions (#3287) 2021-11-26 23:40:58 +01:00
Kevin McKernan
4dc76749f2 fix(providers): Rewrite EVEOnline in TS, fix default scopes (#2759)
* refactor EVEOnlineProvider into typescript, fix default scopes

* Update src/providers/eveonline.ts

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* update to new OIDC SSO endpoints

* set idToken: true

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2021-11-26 19:20:40 +01:00
Torben
35ee608d59 feat(providers): add Osu! provider (#3234) 2021-11-20 14:49:51 +01:00
Estevan Jantsk
0f132de115 feat(providers): add Pipedrive provider (#3011)
* Add Pipedrive as a provider

* convert pipedrive provider to ts

* remove others interface

* refactor(pipedrive): run prettier

Co-authored-by: Lluis Agusti <hi@llu.lu>
2021-11-17 23:07:29 +01:00
Balázs Orbán
31426b9435 fix(providers): match filename with 42 Provider's id (#3225) 2021-11-17 23:03:56 +01:00
Balázs Orbán
64b2a2c43b fix: assert action when req.query isn't available (#3222)
* fix: assert `action` if `req.query` unavailable

* refactor: make `method` externally optional
2021-11-17 22:47:12 +01:00
Balázs Orbán
7beb3ff03b refactor(providers): cleanup 42 (#3221) 2021-11-17 10:15:59 +01:00
Richard van der Dys
432876c011 fix(providers): refactor Zoom
* Added support for zoom in beta

* Converted to typescript

* rename

* Now reflects response from Zoom

* chore: Prettier

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2021-11-16 23:37:03 +01:00
Balázs Orbán
15d1fab4c8 fix: correct assertion when Credentials only (#3217) 2021-11-16 23:16:12 +01:00
Rraji Abdelbari
5e803cd34c refactor(providers): convert 42 to TypeScript (#3211) 2021-11-16 21:57:53 +01:00
Kovacs Nicolas
78fa33312f docs(readme): opencollective domain (#3066)
I had 502 using `opencollective.org` for some time, also, the correct domain looks like `opencollective.com`
2021-11-04 08:16:30 +01:00
Tania
932d05da70 docs: mention other repos in readme and issue forms (#2989)
* Update bug_report.yaml

Add information about distributing issues to the correct repo

* fix yaml syntax

* remove new line

* improve content

* Import content

* remove one emoji

* Update feature_request.yaml

* Update README.md
2021-10-22 09:40:18 +02:00
Gianluca
e8a58a01b6 docs(contributing): fixed numeration type (#2624)
There was a numeration type error in the "For  contributors" section
2021-08-29 11:30:05 +02:00
Nico Domino
91de463a5e docs(providers): add tip about async provider code (#2443) 2021-08-26 23:45:07 +02:00
Nico Domino
4a9d871698 docs(www): add more algolia no-result terms (#2442) 2021-08-26 23:41:49 +02:00
Alex Vilchis
c2119b15de chore(docs): fix dependency name (#2607) 2021-08-26 19:42:20 +02:00
Alex Vilchis
0ce15c4a18 docs: Fix grammar (#2602) 2021-08-25 19:48:14 +02:00
Nico Domino
ead715219a fix(deps): update built-in adapter dependencies (#2589)
* fix(deps): update prisma-legacy-adapter and typeorm-legacy-adapter dependencies

* chore: add missing package-lock update
2021-08-23 21:55:33 +02:00
Ashutosh Kumar
8faa7553dd docs: add suggestions for secret and encryption key generation (#2578) 2021-08-21 23:08:56 +02:00
Eduard Babinyan
90a6a0084b feat(provider): return image for Yandex by default (#2563)
Uploading an user image.
2021-08-20 09:37:30 +02:00
Aaron Powell
cb844a2436 docs(provider): remove en-us from Azure urls (#2554)
MS Docs has a lot of local language translations, so it's best to remove locale information from the URLs so that when someone follows them, they land on the right language version of the content.
2021-08-18 09:46:32 +02:00
Sercan Altundas
74558d6cc2 docs(email): remove duplicate CSS property from html (#2546)
- The CSS property 'text-decoration: none;' was duplicated in the example html code and is removed.
2021-08-17 12:17:54 +02:00
Jaye Hackett
d03125a77b docs(ts): mention module augmentation on callbacks (#2541) 2021-08-17 01:01:19 +02:00
Liam Tait
66d16f8bf4 fix(ts): allow scope as string array type (#2511) 2021-08-12 17:51:31 +02:00
Nico Domino
be74dd0e7e docs(security): email contact update (#2467)
* chore(docs): email contact update

* chore(docs): add me@iaincollins.com back
2021-08-02 17:18:17 +02:00
Aryan Beezadhur
9bf867ddcf docs: Update faq.md (#2458) 2021-07-30 22:34:32 +02:00
Nico Domino
0f460c22da docs(client): add text regarding 'logout' (#2432) 2021-07-28 20:10:08 +02:00
Sigurd Heggemsnes
887cb00877 docs(adapter): Typo in filepath for firebase auth in docs. (#2436) 2021-07-28 12:48:47 +02:00
Douglas
75ca097ff7 docs: Fix link to code (#2405) 2021-07-19 15:36:37 +02:00
Nicolas Azari
bcb9383aec docs: fix typos in options.md (#2393)
* Update options.md

* Update www/docs/configuration/options.md

Co-authored-by: Balázs Orbán <info@balazsorban.com>

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2021-07-17 22:21:45 +02:00
John Michael Kuhn Jr
b953963101 chore(core): fix typo in csrf-token-handler.js where 'strategy' is misspelled (#2391) 2021-07-17 12:02:38 +02:00
Nico Domino
4649f1968b docs(readme): add opencollective details to readme (#2388)
* docs(readme): add opencollective details to readme

* docs(www): add sponsors to docs footer

* docs(readme): move support under ack

* docs(www): dropped docusaurus link in footer
2021-07-16 18:05:15 +02:00
Angelo Annunziata
45f4a69a4e docs(configuration): remove comments in JWT example (#2378) 2021-07-16 09:28:19 +02:00
Prabhdeep Singh
2155c93a3c feat(providers): add OneLogin (#2345)
Co-authored-by: Lluis Agusti <hi@llu.lu>
2021-07-14 11:07:56 +02:00
Angelo Annunziata
d5958571a4 docs(provider): fix typo (#2369) 2021-07-13 21:36:00 +02:00
James Q Quick
ebecaa6a4b docs(adapter): match Fauna index name with implementation(#2360)
* Update Fauna Adapter 

- added one-liner to explain how to use the setup scripts inside of the Fauna dashboard
- updated the `verification_request_by_token` index name to match what is expected inside of the SDK which is `verification_request_by_token_and_identifier`

* Update Typo

Co-authored-by: Balázs Orbán <info@balazsorban.com>

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2021-07-13 17:58:58 +02:00
Vincent Grafé
1c5173a818 docs(callbacks): fix typo (#2363) 2021-07-13 10:24:05 +02:00
Ben Goshow
35ce332cc6 feat(providers): add Freshbooks (#2322)
Contains the following squashed commits: 

* Create freshbooks.js
* Create freshbooks.md
* Update providers.d.ts
* Update freshbooks.md
* Update src/providers/freshbooks.js
* Update providers.test.ts
* Update freshbooks.md
2021-07-11 20:25:26 +02:00
Imamuzzaki Abu Salam
ec295287f1 docs: delete can word in "can can" (#2348) 2021-07-11 15:08:05 +02:00
Nick Arciero
46978ac02f docs(tutorial): Add link to blog post about integrating with Magic (#2340) 2021-07-10 09:56:13 +02:00
Pol
f546e550dd fix(oauth): correctly remove code_verifier cookie when used (#2325)
Co-authored-by: Pol Bonastre <pbonastre@plainconcepts.com>
2021-07-08 17:24:56 +02:00
Balázs Orbán
ac5b4db0f2 chore: add OpenCollective link to FUNDING.yml 2021-07-05 17:54:34 +02:00
Mahieyin Rahmun
8bbffdd08c docs(github): remove title property (#2308) 2021-07-04 13:23:44 +02:00
Mahieyin Rahmun
a22a0a36fd docs(github): remove title prefix and make reproductions required (#2306) 2021-07-04 11:19:13 +02:00
Mahieyin Rahmun
797272afe1 docs: use issue template forms (#2274)
* (docs) initial issue template forms as per #2271

* (typo) fix grammar and typo

* (forms) make the requested changes

* (chore) delete the old .md files

* (forms) fix type key
2021-07-02 21:13:03 +02:00
Mahieyin Rahmun
13e56bcf2f docs(adapters): update outdated documentation (#2296) 2021-07-02 12:50:27 +02:00
yokinist
b0f7f87c04 docs: update 'pages' option in example code (#2270) 2021-07-01 17:12:01 +02:00
Balázs Orbán
9c0851c0f9 chore(ci): shorten names in release.yml workflow 2021-06-30 21:36:28 +02:00
Andriy Komm
f5b3c29ab1 fix(ts): improve authorize typing on Credentials provider (#2227)
Co-authored-by: Balázs Orbán <info@balazsorban.com>
2021-06-30 15:49:38 +02:00
Nico Domino
b4f2a0106a chore(ci): add environment approval (#2214)
Co-authored-by: Balázs Orbán <info@balazsorban.com>
2021-06-30 15:28:12 +02:00
Balázs Orbán
9c095b0532 chore(dev): fix dev app when running locally (#2280)
* fix: fix console warning in dev app

* chore: add `npm i` to `dev:setup` script

* chore(deps): update dev dependencies (react+next)

* chore: update package-lock.json

* chore: use node 16 in actions
2021-06-29 22:11:55 +02:00
Nico Domino
0475964a0f chore(pages): typo in error messages (#2265) 2021-06-28 02:57:35 +02:00
Justin Forlenza
ad6c13cdc9 fix(ts): extend server type in Email provider from nodemailer (#2259)
* Added optional secure & TLS settings for SMTP

* Replaced custom interface with nodemailers

* Fix lockfile version

* Apply suggestions from code review

* Apply suggestions from code review

* Apply suggestions from code review

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2021-06-27 18:51:34 +02:00
Nico Domino
591aa7cc7e docs(adapter): rm @canary from adapters' install instructions (#2260) 2021-06-27 18:28:58 +02:00
ndom91
9abb392b4e chore: fix gh action typo 2021-06-27 03:39:38 +02:00
ndom91
b89ae87fb1 docs: respect color mode 2021-06-27 03:38:04 +02:00
ndom91
3687d17724 Merge branch 'main' of ssh://github.com/nextauthjs/next-auth 2021-06-27 03:11:07 +02:00
Balázs Orbán
b04ff82fb9 chore: clarify where to run envinfo in bug report template 2021-06-24 01:46:02 +02:00
Balázs Orbán
c11915ba9c chore: update bug report template 2021-06-24 01:44:33 +02:00
Balázs Orbán
24ee459f97 chore(ci): run tests and typechecks only 2021-06-24 00:38:17 +02:00
Balázs Orbán
ac4851d238 chore(ci): run test:ci (linting+test+typecheck) 2021-06-24 00:33:32 +02:00
can-mihci
84094b0ee7 docs(client): fix code block typo (#2217) 2021-06-22 20:11:18 +02:00
Vikrant Bhat
f09ab4a04f docs(providers): fix typo (#2220) 2021-06-22 20:08:43 +02:00
Vikrant Bhat
067364381b docs(providers): fix english sentence in Email provider section (#2222) 2021-06-22 09:28:47 +02:00
ndom91
6ee36b6842 ci: test release environment approval 2021-06-18 20:03:07 +02:00
Sangwon Park
5a89ab69d3 feat(provider): add Naver provider (#2172)
* add Naver provider

* fix typo

* Update src/providers/naver.js

Co-authored-by: Balázs Orbán <info@balazsorban.com>

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2021-06-16 00:46:41 +02:00
Balázs Orbán
665445818e docs(config): link to next documentation instead of canary 2021-06-12 17:11:53 +02:00
ndom91
67cf2a11bb docs: fix alt client provider example 2021-06-12 16:42:48 +02:00
52 changed files with 831 additions and 261 deletions

View File

@@ -10,7 +10,13 @@ body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report! Please provide the following information:
Thanks for taking the time to fill out this bug report!
### Important :exclamation:
Please help us maintain this project more efficiently! Before creating the issue make sure you shouldn't be creating it in one the below repos instead:
- Docs related: https://github.com/nextauthjs/docs
- Adapter related: https://github.com/nextauthjs/adapters
If you are in the correct repo, then proceed by providing the following information:
- type: textarea
id: description
attributes:

View File

@@ -9,8 +9,14 @@ body:
- type: markdown
attributes:
value: |
Thank you very much for reaching out to us regarding the awesome feature that you believe should be included in the NextAuth.js library. Please provide the following information:
Thank you very much for reaching out to us regarding the awesome feature that you believe should be included in the NextAuth.js library.
### Important :exclamation:
Please help us maintain this project more efficiently! Before creating the issue make sure you shouldn't be creating it in one the below repos instead:
- Docs related: https://github.com/nextauthjs/docs
- Adapter related: https://github.com/nextauthjs/adapters
If you are in the correct repo, then proceed by providing the following information:
- type: textarea
id: description
attributes:
@@ -65,4 +71,3 @@ body:
attributes:
value: |
It takes a lot of work 🏋🏻‍♀️ maintaining a library like `next-auth`; any contribution is more than welcome 💚

5
.gitignore vendored
View File

@@ -43,6 +43,8 @@ app/package-lock.json
app/yarn.lock
app/prisma/migrations
app/prisma/dev.db*
app/dist
app/next-auth
# VS
/.vs/slnx.sqlite-journal
@@ -50,6 +52,9 @@ app/prisma/dev.db*
/.vs
.vscode
# Jetbrains
.idea
# GitHub Actions runner
/actions-runner
/_work

View File

@@ -32,6 +32,11 @@ NextAuth.js is a complete open source authentication solution for [Next.js](http
It is designed from the ground up to support Next.js and Serverless.
This is the core repo for NextAuth.js. Check the repos below if you are interested in additional information:
- Docs related: https://github.com/nextauthjs/docs
- Adapter related: https://github.com/nextauthjs/adapters
## Getting Started
```
@@ -81,7 +86,8 @@ Advanced options allow you to define your own routines to handle controlling wha
### TypeScript
NextAuth.js comes with built-in types. For more information and usage, check out the [TypeScript section](https://next-auth.js.org/getting-started/typescript) in the documentation.
NextAuth.js comes with built-in types. For more information and usage, check out
the [TypeScript section](https://next-auth.js.org/getting-started/typescript) in the documentation.
## Example
@@ -90,21 +96,24 @@ NextAuth.js comes with built-in types. For more information and usage, check out
```javascript
// pages/api/auth/[...nextauth].js
import NextAuth from "next-auth"
import Providers from "next-auth/providers"
import AppleProvider from "next-auth/providers/apple"
import GoogleProvider from "next-auth/providers/google"
import EmailProvider from "next-auth/providers/email"
export default NextAuth({
secret: process.env.SECRET,
providers: [
// OAuth authentication providers
Providers.Apple({
AppleProvider({
clientId: process.env.APPLE_ID,
clientSecret: process.env.APPLE_SECRET,
}),
Providers.Google({
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
// Sign in with passwordless email link
Providers.Email({
EmailProvider({
server: process.env.MAIL_SERVER,
from: "<no-reply@example.com>",
}),
@@ -219,7 +228,8 @@ We're happy to announce we've recently created an [OpenCollective](https://openc
## Contributing
We're open to all community contributions! If you'd like to contribute in any way, please first read our [Contributing Guide](https://github.com/nextauthjs/next-auth/blob/canary/CONTRIBUTING.md).
We're open to all community contributions! If you'd like to contribute in any way, please first read
our [Contributing Guide](https://github.com/nextauthjs/next-auth/blob/canary/CONTRIBUTING.md).
## License

View File

@@ -2,6 +2,9 @@ const path = require("path")
module.exports = {
webpack(config) {
config.experiments = {
topLevelAwait: true,
}
config.resolve = {
...config.resolve,
alias: {

View File

@@ -23,6 +23,8 @@ import CognitoProvider from "next-auth/providers/cognito"
import SlackProvider from "next-auth/providers/slack"
import Okta from "next-auth/providers/okta"
import AzureB2C from "next-auth/providers/azure-ad-b2c"
import OsuProvider from "next-auth/providers/osu"
import AppleProvider from "next-auth/providers/apple"
// import { PrismaAdapter } from "@next-auth/prisma-adapter"
// import { PrismaClient } from "@prisma/client"
@@ -167,6 +169,14 @@ export const authOptions: NextAuthOptions = {
tenantId: process.env.AZURE_B2C_TENANT_ID,
primaryUserFlow: process.env.AZURE_B2C_PRIMARY_USER_FLOW,
}),
OsuProvider({
clientId: process.env.OSU_CLIENT_ID,
clientSecret: process.env.OSU_CLIENT_SECRET,
}),
AppleProvider({
clientId: process.env.APPLE_ID,
clientSecret: process.env.APPLE_SECRET,
}),
],
secret: process.env.SECRET,
debug: true,

View File

@@ -4,7 +4,7 @@ body {
max-width: 680px;
margin: 0 auto;
background: #fff;
color: #333;
color: var(--color-text);
}
li,

21
package-lock.json generated
View File

@@ -6935,20 +6935,6 @@
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
@@ -20160,13 +20146,6 @@
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",

View File

@@ -5,6 +5,11 @@
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth.git",
"author": "Iain Collins <me@iaincollins.com>",
"contributors": [
"Balázs Orbán <info@balazsorban.com>",
"Nico Domino <yo@ndo.dev>",
"Lluis Agusti <hi@llu.lu>"
],
"main": "index.js",
"module": "index.js",
"types": "index.d.ts",
@@ -139,10 +144,7 @@
"parserOptions": {
"project": "./tsconfig.json"
},
"extends": [
"standard-with-typescript",
"prettier"
],
"extends": ["standard-with-typescript", "prettier"],
"ignorePatterns": [
"node_modules",
"next-env.d.ts",
@@ -166,18 +168,12 @@
},
"overrides": [
{
"files": [
"./**/*test.js"
],
"files": ["./**/*test.js"],
"env": {
"jest/globals": true
},
"extends": [
"plugin:jest/recommended"
],
"plugins": [
"jest"
]
"extends": ["plugin:jest/recommended"],
"plugins": ["jest"]
}
]
},
@@ -211,6 +207,10 @@
{
"type": "github",
"url": "https://github.com/sponsors/balazsorban44"
},
{
"type": "opencollective",
"url": "https://opencollective.com/nextauth"
}
]
}

View File

@@ -13,7 +13,7 @@ import type { ErrorType } from "./pages/error"
export interface IncomingRequest {
/** @default "http://localhost:3000" */
host: string
method: string
method?: string
cookies?: Record<string, string>
headers?: Record<string, any>
query?: Record<string, any>
@@ -67,7 +67,7 @@ export async function NextAuthHandler<
return render.error({ error: "configuration" })
}
const { action, providerId, error } = req
const { action, providerId, error, method = "GET" } = req
const { options, cookies } = await init({
userOptions,
@@ -77,7 +77,7 @@ export async function NextAuthHandler<
callbackUrl: req.body?.callbackUrl ?? req.query?.callbackUrl,
csrfToken: req.body?.csrfToken,
cookies: req.cookies,
isPost: req.method === "POST",
isPost: method === "POST",
})
const sessionStore = new SessionStore(
@@ -86,7 +86,7 @@ export async function NextAuthHandler<
options.logger
)
if (req.method === "GET") {
if (method === "GET") {
const render = renderPage({ ...options, query: req.query, cookies })
const { pages } = options
switch (action) {
@@ -119,9 +119,9 @@ export async function NextAuthHandler<
const callback = await routes.callback({
body: req.body,
query: req.query,
method: req.method,
headers: req.headers,
cookies: req.cookies,
method,
options,
sessionStore,
})
@@ -165,7 +165,7 @@ export async function NextAuthHandler<
return render.error({ error: error as ErrorType })
default:
}
} else if (req.method === "POST") {
} else if (method === "POST") {
switch (action) {
case "signin":
// Verified CSRF Token required for all sign in routes
@@ -201,9 +201,9 @@ export async function NextAuthHandler<
const callback = await routes.callback({
body: req.body,
query: req.query,
method: req.method,
headers: req.headers,
cookies: req.cookies,
method,
options,
sessionStore,
})
@@ -228,6 +228,6 @@ export async function NextAuthHandler<
return {
status: 400,
body: `Error: Action ${action} with HTTP ${req.method} is not supported by NextAuth.js` as any,
body: `Error: Action ${action} with HTTP ${method} is not supported by NextAuth.js` as any,
}
}

View File

@@ -27,7 +27,8 @@ export function assertConfig(
): ConfigError | WarningCode | undefined {
const { options, req } = params
if (!req.query?.nextauth) {
// req.query isn't defined when asserting `getServerSession` for example
if (!req.query?.nextauth && !req.action) {
return new MissingAPIRoute(
"Cannot find [...nextauth].{js,ts} in `/pages/api/auth`. Make sure the filename is written correctly."
)
@@ -55,7 +56,7 @@ export function assertConfig(
const onlyCredentials = !options.providers.some(
(p) => p.type !== "credentials"
)
if (dbStrategy || onlyCredentials) {
if (dbStrategy && onlyCredentials) {
return new UnsupportedStrategy(
"Signin in with credentials only supported if JWT strategy is enabled"
)

View File

@@ -15,7 +15,7 @@ export default async function oAuthCallback(params: {
options: InternalOptions<"oauth">
query: IncomingRequest["query"]
body: IncomingRequest["body"]
method: IncomingRequest["method"]
method: Required<IncomingRequest>["method"]
cookies: IncomingRequest["cookies"]
}): Promise<GetProfileResult & { cookies?: OutgoingResponse["cookies"] }> {
const { options, query, body, method, cookies } = params
@@ -137,10 +137,6 @@ export default async function oAuthCallback(params: {
})
}
// If a user object is supplied (e.g. Apple provider) add it to the profile object
// TODO: Remove/extract to Apple provider?
profile.user = JSON.parse(body?.user ?? query?.user ?? null)
const profileResult = await getProfile({
profile,
provider,

View File

@@ -44,12 +44,7 @@ export async function openidClient(
// and https://github.com/nextauthjs/next-auth/issues/3067
client[custom.clock_tolerance] = 10
// https://github.com/nextauthjs/next-auth/discussions/3186
if (typeof provider.httpOptions?.timeout === "number") {
custom.setHttpOptionsDefaults({
timeout: provider.httpOptions.timeout,
})
}
if (provider.httpOptions) custom.setHttpOptionsDefaults(provider.httpOptions)
return client
}

View File

@@ -87,15 +87,15 @@ export default function ErrorPage(props: ErrorProps) {
status,
html: (
<div className="error">
<style
dangerouslySetInnerHTML={{
__html: `
{ theme?.brandColor && <style
dangerouslySetInnerHTML={{
__html: `
:root {
--brand-color: ${theme?.brandColor};
--brand-color: ${theme?.brandColor}
}
`,
}}
/>
}}
/> }
{theme?.logo && <img src={theme.logo} alt="Logo" className="logo" />}
<div className="card">
<h1>{heading}</h1>

View File

@@ -51,6 +51,7 @@ export default function SigninPage(props: SignInServerPageParams) {
EmailSignin: "Check your email inbox.",
CredentialsSignin:
"Sign in failed. Check the details you provided are correct.",
SessionRequired: "Please sign in to access this page.",
default: "Unable to sign in.",
}
@@ -58,7 +59,8 @@ export default function SigninPage(props: SignInServerPageParams) {
return (
<div className="signin">
<style
{ theme.brandColor && <style
dangerouslySetInnerHTML={{
__html: `
:root {
@@ -66,7 +68,7 @@ export default function SigninPage(props: SignInServerPageParams) {
}
`,
}}
/>
/> }
{theme.logo && <img src={theme.logo} alt="Logo" className="logo" />}
<div className="card">
{error && (

View File

@@ -12,7 +12,7 @@ export default function SignoutPage(props: SignoutProps) {
return (
<div className="signout">
<style
{ theme.brandColor && <style
dangerouslySetInnerHTML={{
__html: `
:root {
@@ -20,7 +20,7 @@ export default function SignoutPage(props: SignoutProps) {
}
`,
}}
/>
/> }
{theme.logo && <img src={theme.logo} alt="Logo" className="logo" />}
<div className="card">
<h1>Signout</h1>

View File

@@ -11,7 +11,7 @@ export default function VerifyRequestPage(props: VerifyRequestPageProps) {
return (
<div className="verify-request">
<style
{ theme.brandColor && <style
dangerouslySetInnerHTML={{
__html: `
:root {
@@ -19,7 +19,7 @@ export default function VerifyRequestPage(props: VerifyRequestPageProps) {
}
`,
}}
/>
/> }
{theme.logo && <img src={theme.logo} alt="Logo" className="logo" />}
<div className="card">
<h1>Check your email</h1>

View File

@@ -11,7 +11,7 @@ import type { User } from "../.."
export default async function callback(params: {
options: InternalOptions<"oauth" | "credentials" | "email">
query: IncomingRequest["query"]
method: IncomingRequest["method"]
method: Required<IncomingRequest>["method"]
body: IncomingRequest["body"]
headers: IncomingRequest["headers"]
cookies: IncomingRequest["cookies"]

View File

@@ -69,7 +69,7 @@ label {
text-align: left;
margin-bottom: 0.25rem;
display: block;
color: #666;
color: var(--color-text);
}
input[type] {
@@ -258,5 +258,5 @@ a.site {
}
.section-header {
color: var(--brand-color);
color: var(--brand-color, var(--color-text));
}

View File

@@ -18,17 +18,18 @@ async function NextAuthNextHandler(
res: NextApiResponse,
options: NextAuthOptions
) {
const { nextauth, ...query } = req.query
const handler = await NextAuthHandler({
req: {
host: (process.env.NEXTAUTH_URL ?? process.env.VERCEL_URL) as string,
body: req.body,
query: req.query,
query,
cookies: req.cookies,
headers: req.headers,
method: req.method ?? "GET",
action: req.query.nextauth[0] as NextAuthAction,
providerId: req.query.nextauth[1],
error: (req.query.error as string | undefined) ?? req.query.nextauth[1],
method: req.method,
action: nextauth?.[0] as NextAuthAction,
providerId: nextauth?.[1],
error: (req.query.error as string | undefined) ?? nextauth?.[1],
},
options,
})

179
src/providers/42-school.ts Normal file
View File

@@ -0,0 +1,179 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface UserData {
id: number
email: string
login: string
first_name: string
last_name: string
usual_full_name: null | string
usual_first_name: null | string
url: string
phone: "hidden" | string | null
displayname: string
image_url: string | null
"staff?": boolean
correction_point: number
pool_month: string | null
pool_year: string | null
location: string | null
wallet: number
anonymize_date: string
created_at: string
updated_at: string | null
alumni: boolean
"is_launched?": boolean
}
export interface CursusUser {
grade: string | null
level: number
skills: Array<{ id: number; name: string; level: number }>
blackholed_at: string | null
id: number
begin_at: string | null
end_at: string | null
cursus_id: number
has_coalition: boolean
created_at: string
updated_at: string | null
user: UserData
cursus: { id: number; created_at: string; name: string; slug: string }
}
export interface ProjectUser {
id: number
occurrence: number
final_mark: number | null
status: "in_progress" | "finished"
"validated?": boolean | null
current_team_id: number
project: {
id: number
name: string
slug: string
parent_id: number | null
}
cursus_ids: number[]
marked_at: string | null
marked: boolean
retriable_at: string | null
created_at: string
updated_at: string | null
}
export interface Achievement {
id: number
name: string
description: string
tier: "none" | "easy" | "medium" | "hard" | "challenge"
kind: "scolarity" | "project" | "pedagogy" | "scolarity"
visible: boolean
image: string | null
nbr_of_success: number | null
users_url: string
}
export interface LanguagesUser {
id: number
language_id: number
user_id: number
position: number
created_at: string
}
export interface TitlesUser {
id: number
user_id: number
title_id: number
selected: boolean
created_at: string
updated_at: string | null
}
export interface ExpertisesUser {
id: number
expertise_id: number
interested: boolean
value: number
contact_me: boolean
created_at: string
user_id: number
}
export interface Campus {
id: number
name: string
time_zone: string
language: {
id: number
name: string
identifier: string
created_at: string
updated_at: string | null
}
users_count: number
vogsphere_id: number
country: string
address: string
zip: string
city: string
website: string
facebook: string
twitter: string
active: boolean
email_extension: string
default_hidden_phone: boolean
}
export interface CampusUser {
id: number
user_id: number
campus_id: number
is_primary: boolean
created_at: string
updated_at: string | null
}
export interface FortyTwoProfile extends UserData {
groups: Array<{ id: string; name: string }>
cursus_users: CursusUser[]
projects_users: ProjectUser[]
languages_users: LanguagesUser[]
achievements: Achievement[]
titles: Array<{ id: string; name: string }>
titles_users: TitlesUser[]
partnerships: any[]
patroned: any[]
patroning: any[]
expertises_users: ExpertisesUser[]
roles: Array<{ id: string; name: string }>
campus: Campus[]
campus_users: CampusUser[]
user: any | null
}
export default function FortyTwo<
P extends Record<string, any> = FortyTwoProfile
>(options: OAuthUserConfig<P>): OAuthConfig<P> {
return {
id: "42-school",
name: "42 School",
type: "oauth",
authorization: {
url: "https://api.intra.42.fr/oauth/authorize",
params: { scope: "public" },
},
token: "https://api.intra.42.fr/oauth/token",
userinfo: "https://api.intra.42.fr/v2/me",
profile(profile) {
return {
id: profile.id,
name: profile.usual_full_name,
email: profile.email,
image: profile.image_url,
}
},
options,
}
}

View File

@@ -1,23 +0,0 @@
/** @type {import(".").OAuthProvider} */
export default function FortyTwo(options) {
return {
id: "42-school",
name: "42 School",
type: "oauth",
authorization: {
url: "https://api.intra.42.fr/oauth/authorize",
params: { scope: "public" },
},
token: "https://api.intra.42.fr/oauth/token",
userinfo: "https://api.intra.42.fr/v2/me",
profile(profile) {
return {
id: profile.id,
name: profile.usual_full_name,
email: profile.email,
image: profile.image_url,
}
},
options,
}
}

View File

@@ -1,37 +0,0 @@
/** @type {import(".").OAuthProvider} */
export default function Apple(options) {
return {
id: "apple",
name: "Apple",
type: "oauth",
authorization: {
url: "https://appleid.apple.com/auth/authorize",
params: {
scope: "name email",
response_type: "code",
id_token: "",
response_mode: "form_post",
},
},
token: {
url: "https://appleid.apple.com/auth/token",
idToken: true,
},
jwks_endpoint: "https://appleid.apple.com/auth/keys",
profile(profile) {
// The name of the user will only be returned on first login
const name = profile.user
? profile.user.name.firstName + " " + profile.user.name.lastName
: null
return {
id: profile.sub,
name,
email: profile.email,
image: null,
}
},
checks: ["none"], // REVIEW: Apple does not support state, as far as I know. Can we use "pkce" then?
options,
}
}

122
src/providers/apple.ts Normal file
View File

@@ -0,0 +1,122 @@
import { OAuthConfig, OAuthUserConfig } from "."
/**
* See more at:
* [Retrieve the User's Information from Apple ID Servers
](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/authenticating_users_with_sign_in_with_apple#3383773)
*/
export interface AppleProfile {
/**
* The issuer registered claim identifies the principal that issued the identity token.
* Since Apple generates the token, the value is `https://appleid.apple.com`.
*/
iss: "https://appleid.apple.com"
/**
* The audience registered claim identifies the recipient for which the identity token is intended.
* Since the token is meant for your application, the value is the `client_id` from your developer account.
*/
aud: string
/**
* The issued at registered claim indicates the time at which Apple issued the identity token,
* in terms of the number of seconds since Epoch, in UTC.
*/
iat: number
/**
* The expiration time registered identifies the time on or after which the identity token expires,
* in terms of number of seconds since Epoch, in UTC.
* The value must be greater than the current date/time when verifying the token.
*/
exp: number
/**
* The subject registered claim identifies the principal that's the subject of the identity token.
* Since this token is meant for your application, the value is the unique identifier for the user.
*/
sub: string
/**
* A String value used to associate a client session and the identity token.
* This value mitigates replay attacks and is present only if passed during the authorization request.
*/
nonce: string
/**
* A Boolean value that indicates whether the transaction is on a nonce-supported platform.
* If you sent a nonce in the authorization request but don't see the nonce claim in the identity token,
* check this claim to determine how to proceed.
* If this claim returns true, you should treat nonce as mandatory and fail the transaction;
* otherwise, you can proceed treating the nonce as options.
*/
nonce_supported: boolean
/**
* A String value representing the user's email address.
* The email address is either the user's real email address or the proxy address,
* depending on their status private email relay service.
*/
email: string
/**
* A String or Boolean value that indicates whether the service has verified the email.
* The value of this claim is always true, because the servers only return verified email addresses.
* The value can either be a String (`"true"`) or a Boolean (`true`).
*/
email_verified: "true" | true
/**
* A String or Boolean value that indicates whether the email shared by the user is the proxy address.
* The value can either be a String (`"true"` or `"false"`) or a Boolean (`true` or `false`).
*/
is_private_email: boolean | "true" | "false"
/**
* An Integer value that indicates whether the user appears to be a real person.
* Use the value of this claim to mitigate fraud. The possible values are: 0 (or Unsupported), 1 (or Unknown), 2 (or LikelyReal).
* For more information, see [`ASUserDetectionStatus`](https://developer.apple.com/documentation/authenticationservices/asuserdetectionstatus).
* This claim is present only on iOS 14 and later, macOS 11 and later, watchOS 7 and later, tvOS 14 and later;
* the claim isn't present or supported for web-based apps.
*/
real_user_status: 0 | 1 | 2
/**
* A String value representing the transfer identifier used to migrate users to your team.
* This claim is present only during the 60-day transfer period after an you transfer an app.
* For more information, see [Bringing New Apps and Users into Your Team](https://developer.apple.com/documentation/sign_in_with_apple/bringing_new_apps_and_users_into_your_team).
*/
transfer_sub: string
at_hash: string
auth_time: number
}
export default function Apple<P extends Record<string, any> = AppleProfile>(
options: Omit<OAuthUserConfig<P>, "clientSecret"> & {
/**
* Apple requires the client secret to be a JWT. You can generate one using the following script:
* https://bal.so/apple-gen-secret
*
* Read more: [Creating the Client Secret
](https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens#3262048)
*/
clientSecret: string
}
): OAuthConfig<P> {
return {
id: "apple",
name: "Apple",
type: "oauth",
wellKnown: "https://appleid.apple.com/.well-known/openid-configuration",
authorization: {
params: { scope: "name email", response_mode: "form_post" },
},
idToken: true,
profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: null,
}
},
checks: ["pkce"],
options,
}
}

View File

@@ -1,11 +1,21 @@
/** @type {import(".").OAuthProvider} */
export default function Atlassian(options) {
import type { OAuthConfig, OAuthUserConfig } from "."
interface AtlassianProfile {
account_id: string
name: string
email: string
picture: string
}
export default function Atlassian<P extends AtlassianProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "atlassian",
name: "Atlassian",
type: "oauth",
authorization: {
url: "https://auth.atlassian.com/oauth/authorize",
url: "https://auth.atlassian.com/authorize",
params: {
audience: "api.atlassian.com",
prompt: "consent",

View File

@@ -1,4 +1,4 @@
import { OAuthConfig, OAuthUserConfig } from "./oauth"
import type { OAuthConfig, OAuthUserConfig } from "."
export interface Auth0Profile {
sub: string

View File

@@ -1,4 +1,4 @@
import { OAuthConfig, OAuthUserConfig } from "."
import type { OAuthConfig, OAuthUserConfig } from "."
export interface AzureB2CProfile {
exp: number

View File

@@ -1,4 +1,4 @@
import { OAuthConfig, OAuthUserConfig } from "./oauth"
import type { OAuthConfig, OAuthUserConfig } from "."
export interface AzureADProfile {
sub: string

View File

@@ -1,4 +1,4 @@
import { OAuthConfig, OAuthUserConfig } from "."
import type { OAuthConfig, OAuthUserConfig } from "."
export interface CognitoProfile {
sub: string

View File

@@ -1,6 +1,6 @@
import { IncomingRequest } from "src/core"
import { CommonProviderOptions } from "."
import { User, Awaitable } from ".."
import type { IncomingRequest } from "../core"
import type { CommonProviderOptions } from "."
import type { User, Awaitable } from ".."
export interface CredentialInput {
label?: string

View File

@@ -1,8 +1,8 @@
import { createTransport } from "nodemailer"
import { CommonProviderOptions } from "."
import { Options as SMTPConnectionOptions } from "nodemailer/lib/smtp-connection"
import { Awaitable } from ".."
import type { CommonProviderOptions } from "."
import type { Options as SMTPConnectionOptions } from "nodemailer/lib/smtp-connection"
import type { Awaitable } from ".."
export interface EmailConfig extends CommonProviderOptions {
type: "email"

View File

@@ -1,20 +0,0 @@
/** @type {import(".").OAuthProvider} */
export default function EVEOnline(options) {
return {
id: "eveonline",
name: "EVE Online",
type: "oauth",
authorization: "https://login.eveonline.com/oauth/authorize",
token: "https://login.eveonline.com/oauth/token",
userinfo: "https://login.eveonline.com/oauth/verify",
profile(profile) {
return {
id: profile.CharacterID,
name: profile.CharacterName,
email: null,
image: `https://image.eveonline.com/Character/${profile.CharacterID}_128.jpg`,
}
},
options,
}
}

View File

@@ -0,0 +1,38 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface EVEOnlineProfile {
CharacterID: number
CharacterName: string
ExpiresOn: string
Scopes: string
TokenType: string
CharacterOwnerHash: string
IntellectualProperty: string
}
export default function EVEOnline<
P extends Record<string, any> = EVEOnlineProfile
>(options: OAuthUserConfig<P>): OAuthConfig<P> {
return {
id: "eveonline",
name: "EVE Online",
type: "oauth",
wellKnown:
"https://login.eveonline.com/.well-known/oauth-authorization-server",
authorization: {
params: {
scope: "publicData",
},
},
idToken: true,
profile(profile) {
return {
id: profile.CharacterID,
name: profile.CharacterName,
email: null,
image: `https://image.eveonline.com/Character/${profile.CharacterID}_128.jpg`,
}
},
options,
}
}

View File

@@ -1,7 +1,6 @@
import { Profile } from ".."
import { OAuthConfig, OAuthUserConfig } from "./oauth"
import type { OAuthConfig, OAuthUserConfig } from "."
export interface FacebookProfile extends Profile {
export interface FacebookProfile {
id: string
picture: { data: { url: string } }
}

View File

@@ -6,7 +6,31 @@ export default function GitHub(options) {
type: "oauth",
authorization: "https://github.com/login/oauth/authorize?scope=read:user+user:email",
token: "https://github.com/login/oauth/access_token",
userinfo: "https://api.github.com/user",
userinfo: {
url: "https://api.github.com/user",
async request({ client, tokens }) {
// Get base profile
const profile = await client.userinfo(tokens)
// If user has email hidden, get their primary email from the GitHub API
if (!profile.email) {
const emails = await (
await fetch("https://api.github.com/user/emails", {
headers: { Authorization: `token ${tokens.access_token}` },
})
).json()
if (emails?.length > 0) {
// Get primary email
profile.email = emails.find(email => email.primary)?.email;
// And if for some reason it doesn't exist, just use the first
if (!profile.email) profile.email = emails[0].email;
}
}
return profile
},
},
profile(profile) {
return {
id: profile.id.toString(),

View File

@@ -1,4 +1,4 @@
import { OAuthConfig, OAuthUserConfig } from "./oauth"
import type { OAuthConfig, OAuthUserConfig } from "."
export interface GoogleProfile {
sub: string

View File

@@ -1,8 +1,8 @@
import { OAuthConfig, OAuthProvider, OAuthProviderType } from "./oauth"
import type { OAuthConfig, OAuthProvider, OAuthProviderType } from "./oauth"
import { EmailConfig, EmailProvider, EmailProviderType } from "./email"
import type { EmailConfig, EmailProvider, EmailProviderType } from "./email"
import {
import type {
CredentialsConfig,
CredentialsProvider,
CredentialsProviderType,

View File

@@ -1,4 +1,4 @@
import { OAuthConfig, OAuthUserConfig } from "."
import type { OAuthConfig, OAuthUserConfig } from "."
export interface KeycloakProfile {
exp: number

View File

@@ -1,20 +1,20 @@
import { OAuthConfig, OAuthUserConfig } from "."
import type { OAuthConfig, OAuthUserConfig } from "."
export interface LineProfile {
iss: string;
sub: string;
aud: string;
exp: number;
iat: number;
amr: string[];
name: string;
picture: string;
user: any;
iss: string
sub: string
aud: string
exp: number
iat: number
amr: string[]
name: string
picture: string
user: any
}
export default function LINE<
P extends Record<string, any> = LineProfile
>(options: OAuthUserConfig<P>): OAuthConfig<P> {
export default function LINE<P extends Record<string, any> = LineProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "line",
name: "LINE",
@@ -33,6 +33,6 @@ export default function LINE<
client: {
id_token_signed_response_alg: "HS256",
},
options
options,
}
}

View File

@@ -1,4 +1,4 @@
import { OAuthConfig, OAuthUserConfig } from "."
import type { OAuthConfig, OAuthUserConfig } from "."
interface Identifier {
identifier: string

View File

@@ -1,5 +1,5 @@
import { CommonProviderOptions } from "../providers"
import { Profile, TokenSet, User, Awaitable } from ".."
import type { CommonProviderOptions } from "../providers"
import type { Profile, TokenSet, User, Awaitable } from ".."
import type {
AuthorizationParameters,
@@ -115,10 +115,7 @@ export interface OAuthConfig<P> extends CommonProviderOptions, PartialIssuer {
client?: Partial<ClientMetadata>
jwks?: { keys: JWK[] }
clientId?: string
clientSecret?:
| string
// TODO: only allow for Apple
| Record<"appleId" | "teamId" | "privateKey" | "keyId", string>
clientSecret?: string
/**
* If set to `true`, the user information will be extracted
* from the `id_token` claims, instead of
@@ -134,7 +131,7 @@ export interface OAuthConfig<P> extends CommonProviderOptions, PartialIssuer {
// TODO: only allow for some
issuer?: string
/** Read more at: https://github.com/panva/node-openid-client/tree/main/docs#customizing-http-requests */
httpOptions?: Pick<HttpOptions, "timeout">
httpOptions?: HttpOptions
/**
* The options provided by the user.

View File

@@ -1,4 +1,4 @@
import { OAuthConfig, OAuthUserConfig } from "."
import type { OAuthConfig, OAuthUserConfig } from "."
export interface OktaProfile {
iss: string

77
src/providers/osu.ts Normal file
View File

@@ -0,0 +1,77 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface OsuUserCompact {
avatar_url: string
country_code: string
default_group: string
id: string
is_active: boolean
is_bot: boolean
is_deleted: boolean
is_online: boolean
is_supporter: boolean
last_visit: Date | null
pm_friends_only: boolean
profile_colour: string | null
username: string
}
export interface OsuProfile extends OsuUserCompact {
discord: string | null
has_supported: boolean
interests: string | null
join_date: Date
kudosu: {
available: number
total: number
}
location: string | null
max_blocks: number
max_friends: number
occupation: string | null
playmode: string
playstyle: string[]
post_count: number
profile_order: string[]
title: string | null
title_url: string | null
twitter: string | null
website: string | null
country: {
code: string
name: string
}
cover: {
custom_url: string | null
url: string
id: number | null
}
is_restricted: boolean
}
export default function Osu<P extends Record<string, any> = OsuProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "osu",
name: "Osu!",
type: "oauth",
token: "https://osu.ppy.sh/oauth/token",
authorization: {
url: "https://osu.ppy.sh/oauth/authorize",
params: {
scope: "identify",
},
},
userinfo: "https://osu.ppy.sh/api/v2/me",
profile(profile) {
return {
id: profile.id,
email: null,
name: profile.username,
image: profile.avatar_url,
}
},
options,
}
}

View File

@@ -0,0 +1,59 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface PipedriveProfile {
success: boolean
data: {
id: number
name: string
default_currency?: string
locale?: string
lang?: number
email: string
phone?: string
activated?: boolean
last_login?: Date
created?: Date
modified?: Date
signup_flow_variation?: string
has_created_company?: boolean
is_admin?: number
active_flag?: boolean
timezone_name?: string
timezone_offset?: string
role_id?: number
icon_url?: string
is_you?: boolean
company_id?: number
company_name?: string
company_domain?: string
company_country?: string
company_industry?: string
language?: {
language_code?: string
country_code?: string
}
}
}
export default function Pipedrive<
P extends Record<string, any> = PipedriveProfile
>(options: OAuthUserConfig<P>): OAuthConfig<P> {
return {
id: "pipedrive",
name: "Pipedrive",
type: "oauth",
version: "2.0",
authorization: "https://oauth.pipedrive.com/oauth/authorize",
token: "https://oauth.pipedrive.com/oauth/token",
userinfo: "https://api.pipedrive.com/users/me",
profile: ({ data: profile }) => {
return {
id: profile.id,
name: profile.name,
email: profile.email,
image: profile.icon_url,
}
},
options,
}
}

View File

@@ -1,4 +1,4 @@
import { OAuthConfig, OAuthUserConfig } from "."
import type { OAuthConfig, OAuthUserConfig } from "."
export interface SlackProfile {
ok: boolean

View File

@@ -1,4 +1,4 @@
import { OAuthConfig, OAuthUserConfig } from "."
import type { OAuthConfig, OAuthUserConfig } from "."
export interface SpotifyImage {
url: string

View File

@@ -1,4 +1,4 @@
import { OAuthConfig, OAuthUserConfig } from "."
import type { OAuthConfig, OAuthUserConfig } from "."
export interface TwitchProfile {
sub: string

View File

@@ -1,26 +0,0 @@
/** @type {import(".").OAuthProvider} */
export default function Twitter(options) {
return {
id: "twitter",
name: "Twitter",
type: "oauth",
version: "1.0A",
authorization: "https://api.twitter.com/oauth/authenticate",
accessTokenUrl: "https://api.twitter.com/oauth/access_token",
requestTokenUrl: "https://api.twitter.com/oauth/request_token",
profileUrl:
"https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true",
profile(profile) {
return {
id: profile.id_str,
name: profile.name,
email: profile.email,
image: profile.profile_image_url_https.replace(
/_normal\.(jpg|png|gif)$/,
".$1"
),
}
},
options,
}
}

124
src/providers/twitter.ts Normal file
View File

@@ -0,0 +1,124 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface TwitterProfile {
id: number
id_str: string
name: string
screen_name: string
location: string
description: string
url: string
entities: {
url: {
urls: Array<{
url: string
expanded_url: string
display_url: string
indices: number[]
}>
}
description: {
urls: any[]
}
}
protected: boolean
followers_count: number
friends_count: number
listed_count: number
created_at: string
favourites_count: number
utc_offset?: any
time_zone?: any
geo_enabled: boolean
verified: boolean
statuses_count: number
lang?: any
status: {
created_at: string
id: number
id_str: string
text: string
truncated: boolean
entities: {
hashtags: any[]
symbols: any[]
user_mentions: Array<{
screen_name: string
name: string
id: number
id_str: string
indices: number[]
}>
urls: any[]
}
source: string
in_reply_to_status_id: number
in_reply_to_status_id_str: string
in_reply_to_user_id: number
in_reply_to_user_id_str: string
in_reply_to_screen_name: string
geo?: any
coordinates?: any
place?: any
contributors?: any
is_quote_status: boolean
retweet_count: number
favorite_count: number
favorited: boolean
retweeted: boolean
lang: string
}
contributors_enabled: boolean
is_translator: boolean
is_translation_enabled: boolean
profile_background_color: string
profile_background_image_url: string
profile_background_image_url_https: string
profile_background_tile: boolean
profile_image_url: string
profile_image_url_https: string
profile_banner_url: string
profile_link_color: string
profile_sidebar_border_color: string
profile_sidebar_fill_color: string
profile_text_color: string
profile_use_background_image: boolean
has_extended_profile: boolean
default_profile: boolean
default_profile_image: boolean
following: boolean
follow_request_sent: boolean
notifications: boolean
translator_type: string
withheld_in_countries: any[]
suspended: boolean
needs_phone_verification: boolean
}
export default function Twitter<P extends Record<string, any> = TwitterProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "twitter",
name: "Twitter",
type: "oauth",
version: "1.0A",
authorization: "https://api.twitter.com/oauth/authenticate",
accessTokenUrl: "https://api.twitter.com/oauth/access_token",
requestTokenUrl: "https://api.twitter.com/oauth/request_token",
profileUrl:
"https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true",
profile(profile) {
return {
id: profile.id_str,
name: profile.name,
email: profile.email,
image: profile.profile_image_url_https.replace(
/_normal\.(jpg|png|gif)$/,
".$1"
),
}
},
options,
}
}

View File

@@ -13,7 +13,9 @@ export default function Yandex(options) {
id: profile.id,
name: profile.real_name,
email: profile.default_email,
image: profile.is_avatar_empty ? null : `https://avatars.yandex.net/get-yapic/${profile.default_avatar_id}/islands-200`,
image: profile.is_avatar_empty
? null
: `https://avatars.yandex.net/get-yapic/${profile.default_avatar_id}/islands-200`,
}
},
options,

View File

@@ -1,20 +0,0 @@
/** @type {import(".").OAuthProvider} */
export default function Zoom(options) {
return {
id: "zoom",
name: "Zoom",
type: "oauth",
authorization: "https://zoom.us/oauth/authorize",
token: "https://zoom.us/oauth/token",
userinfo: "https://api.zoom.us/v2/users/me",
profile(profile) {
return {
id: profile.id,
name: `${profile.first_name} ${profile.last_name}`,
email: profile.email,
image: null,
}
},
options,
}
}

52
src/providers/zoom.ts Normal file
View File

@@ -0,0 +1,52 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface ZoomProfile {
id: string
first_name: string
last_name: string
email: string
type: number
role_name: string
pmi: number
use_pmi: boolean
vanity_url: string
personal_meeting_url: string
timezone: string
verified: number
dept: string
created_at: string
last_login_time: string
last_client_version: string
pic_url: string
host_key: string
jid: string
group_ids: string[]
im_group_ids: string[]
account_id: string
language: string
phone_country: string
phone_number: string
status: string
}
export default function Zoom<P extends Record<string, any> = ZoomProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "zoom",
name: "Zoom",
type: "oauth",
authorization: "https://zoom.us/oauth/authorize?scope",
token: "https://zoom.us/oauth/token",
userinfo: "https://api.zoom.us/v2/users/me",
profile(profile) {
return {
id: profile.id,
name: `${profile.first_name} ${profile.last_name}`,
email: profile.email,
image: profile.pic_url,
}
},
options,
}
}