Merge branch 'main' into feature/discord-provider

This commit is contained in:
Alexander Staroselsky 2022-03-27 09:22:45 -06:00
commit 9a8ffa9397
15 changed files with 2942 additions and 2803 deletions

View File

@ -1,3 +1,4 @@
README.md
app/
*.cjs
dist/

2
.gitignore vendored
View File

@ -76,4 +76,4 @@ typings/
.fusebox/
# Build output
dist/*
dist/

View File

@ -144,6 +144,10 @@ SvelteKitAuth is inspired by the [NextAuth.js](https://next-auth.js.org/) packag
As it leverages classes and Typescript, the implementation of such providers is very straightforward, and in the future it will even be possible to register multiple SvelteKitAuth handlers in the same project, should the need arise, by leveraging a class-based client and server setup.
## Examples
Looking for help? Check out the [example app](./app/) in the repository source. Make something cool you want to show off? Share it with others [in the discussion section](https://github.com/Dan6erbond/sk-auth/discussions/72).
## Contributing
🚧 Work in Progress!

View File

@ -1,2 +1,6 @@
node_modules/
README.md
*.cjs
.svelte-kit/
static/
build/

View File

@ -9,7 +9,7 @@
"format": "prettier --write --plugin-search-dir=. ."
},
"devDependencies": {
"@sveltejs/kit": "next",
"@sveltejs/kit": "^1.0.0-next.259",
"@types/prismjs": "^1.16.5",
"@typescript-eslint/eslint-plugin": "^4.19.0",
"@typescript-eslint/parser": "^4.19.0",

View File

@ -1,13 +1,15 @@
import type { Handle } from "@sveltejs/kit";
import { appAuth } from "$lib/appAuth";
export const handle: Handle = async ({ request, render }) => {
export const handle: Handle = async ({ event, resolve }) => {
// TODO https://github.com/sveltejs/kit/issues/1046
if (request.query.has("_method")) {
request.method = request.query.get("_method").toUpperCase();
if (event.request.query.has("_method")) {
event.request.method = event.request.query.get("_method").toUpperCase();
}
const response = await render(request);
const response = await resolve(event);
return response;
};

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "sk-auth",
"version": "0.4.0",
"version": "0.4.1",
"description": "Authentication library for use with SvelteKit featuring built-in OAuth providers and zero restriction customization!",
"main": "dist/index.js",
"module": "dist/index.esm.js",
@ -18,6 +18,7 @@
"README.md"
],
"scripts": {
"prepare": "npm run build",
"build": "rollup --config",
"dev": "rollup --config --watch",
"test": "echo \"Error: no test specified\" && exit 1",
@ -47,7 +48,7 @@
},
"devDependencies": {
"@rollup/plugin-typescript": "^8.2.1",
"@sveltejs/kit": "^1.0.0-next.211",
"@sveltejs/kit": "^1.0.0-next.259",
"@types/jsonwebtoken": "^8.5.1",
"@typescript-eslint/eslint-plugin": "^4.23.0",
"@typescript-eslint/parser": "^4.23.0",

View File

@ -1,7 +1,6 @@
import type { GetSession, RequestHandler } from "@sveltejs/kit";
import type { EndpointOutput } from "@sveltejs/kit/types/endpoint";
import { RequestHeaders } from "@sveltejs/kit/types/helper";
import { ServerRequest } from "@sveltejs/kit/types/hooks";
import { RequestEvent } from "@sveltejs/kit/types/hooks";
import cookie from "cookie";
import * as jsonwebtoken from "jsonwebtoken";
import type { JWT, Session } from "./interfaces";
@ -45,12 +44,12 @@ export class Auth {
return "svelte_auth_secret";
}
async getToken(headers: RequestHeaders) {
if (!headers.cookie) {
async getToken(headers: any) {
if (!headers.get("cookie")) {
return null;
}
const cookies = cookie.parse(headers.cookie);
const cookies = cookie.parse(headers.get("cookie"));
if (!cookies.svelteauthjwt) {
return null;
@ -71,7 +70,7 @@ export class Auth {
}
getBaseUrl(host?: string) {
const protocol = this.config?.protocol ?? "http";
const protocol = this.config?.protocol ?? "https";
host = this.config?.host ?? host;
return `${protocol}://${host}`;
}
@ -86,7 +85,7 @@ export class Auth {
return new URL(pathname, this.getBaseUrl(host)).href;
}
setToken(headers: RequestHeaders, newToken: JWT | any) {
setToken(headers: any, newToken: JWT | any) {
const originalToken = this.getToken(headers);
return {
@ -113,12 +112,10 @@ export class Auth {
return redirect;
}
async handleProviderCallback(
request: ServerRequest,
provider: Provider,
): Promise<EndpointOutput> {
const { headers, url } = request;
const [profile, redirectUrl] = await provider.callback(request, this);
async handleProviderCallback(event: RequestEvent, provider: Provider): Promise<EndpointOutput> {
const { headers } = event.request;
const { url } = event;
const [profile, redirectUrl] = await provider.callback(event, this);
let token = (await this.getToken(headers)) ?? { user: {} };
if (this.config?.callbacks?.jwt) {
@ -139,11 +136,12 @@ export class Auth {
};
}
async handleEndpoint(request: ServerRequest): Promise<EndpointOutput> {
const { headers, method, url } = request;
async handleEndpoint(event: RequestEvent): Promise<EndpointOutput> {
const { headers, method } = event.request;
const { url } = event;
if (url.pathname === this.getPath("signout")) {
const token = this.setToken(headers, {});
const token = this.setToken(event.request.headers, {});
const jwt = this.signToken(token);
if (method === "POST") {
@ -177,9 +175,9 @@ export class Auth {
);
if (provider) {
if (match.groups.method === "signin") {
return await provider.signin(request, this);
return await provider.signin(event, this);
} else {
return await this.handleProviderCallback(request, provider);
return await this.handleProviderCallback(event, provider);
}
}
}
@ -190,13 +188,13 @@ export class Auth {
};
}
get: RequestHandler = async (request) => {
const { url } = request;
get: RequestHandler = async (event: RequestEvent): Promise<any> => {
const { url } = event;
if (url.pathname === this.getPath("csrf")) {
return { body: "1234" }; // TODO: Generate real token
} else if (url.pathname === this.getPath("session")) {
const session = await this.getSession(request);
const session = await this.getSession(event);
return {
body: {
session,
@ -204,15 +202,16 @@ export class Auth {
};
}
return await this.handleEndpoint(request);
return await this.handleEndpoint(event);
};
post: RequestHandler = async (request) => {
return await this.handleEndpoint(request);
post: RequestHandler = async (event: RequestEvent) => {
return await this.handleEndpoint(event);
};
getSession: GetSession = async ({ headers }) => {
const token = await this.getToken(headers);
getSession: GetSession = async (event: RequestEvent) => {
const { request } = event;
const token = await this.getToken(request.headers);
if (token) {
if (this.config?.callbacks?.session) {

View File

@ -1,5 +1,5 @@
import type { EndpointOutput } from "@sveltejs/kit";
import { ServerRequest } from "@sveltejs/kit/types/hooks";
import { RequestEvent } from "@sveltejs/kit/types/hooks";
import type { Auth } from "../auth";
import type { CallbackResult } from "../types";
@ -28,12 +28,12 @@ export abstract class Provider<T extends ProviderConfig = ProviderConfig> {
}
abstract signin<Locals extends Record<string, any> = Record<string, any>, Body = unknown>(
request: ServerRequest<Locals, Body>,
event: RequestEvent,
svelteKitAuth: Auth,
): EndpointOutput | Promise<EndpointOutput>;
abstract callback<Locals extends Record<string, any> = Record<string, any>, Body = unknown>(
request: ServerRequest<Locals, Body>,
event: RequestEvent,
svelteKitAuth: Auth,
): CallbackResult | Promise<CallbackResult>;
}

View File

@ -1,5 +1,5 @@
import type { EndpointOutput } from "@sveltejs/kit/types/endpoint";
import { ServerRequest } from "@sveltejs/kit/types/hooks";
import { RequestEvent } from "@sveltejs/kit/types/hooks";
import type { Auth } from "../auth";
import type { CallbackResult } from "../types";
import { Provider, ProviderConfig } from "./base";
@ -25,7 +25,7 @@ export abstract class OAuth2BaseProvider<
T extends OAuth2BaseProviderConfig,
> extends Provider<T> {
abstract getAuthorizationUrl(
request: ServerRequest,
event: RequestEvent,
auth: Auth,
state: string,
nonce: string,
@ -33,14 +33,15 @@ export abstract class OAuth2BaseProvider<
abstract getTokens(code: string, redirectUri: string): TokensType | Promise<TokensType>;
abstract getUserProfile(tokens: any): ProfileType | Promise<ProfileType>;
async signin(request: ServerRequest, auth: Auth): Promise<EndpointOutput> {
const { method, url } = request;
async signin(event: RequestEvent, auth: Auth): Promise<EndpointOutput> {
const { method } = event.request;
const { url } = event;
const state = [
`redirect=${url.searchParams.get("redirect") ?? this.getUri(auth, "/", url.host)}`,
].join(",");
const base64State = Buffer.from(state).toString("base64");
const nonce = Math.round(Math.random() * 1000).toString(); // TODO: Generate random based on user values
const authUrl = await this.getAuthorizationUrl(request, auth, base64State, nonce);
const authUrl = await this.getAuthorizationUrl(event, auth, base64State, nonce);
if (method === "POST") {
return {
@ -68,7 +69,8 @@ export abstract class OAuth2BaseProvider<
}
}
async callback({ url }: ServerRequest, auth: Auth): Promise<CallbackResult> {
async callback(event: RequestEvent, auth: Auth): Promise<any> {
const { request, url } = event;
const code = url.searchParams.get("code");
const redirect = this.getStateValue(url.searchParams, "redirect");

View File

@ -1,4 +1,4 @@
import { ServerRequest } from "@sveltejs/kit/types/hooks";
import { RequestEvent } from "@sveltejs/kit/types/hooks";
import type { Auth } from "../auth";
import { ucFirst } from "../helpers";
import { OAuth2BaseProvider, OAuth2BaseProviderConfig, OAuth2Tokens } from "./oauth2.base";
@ -37,7 +37,7 @@ export class OAuth2Provider<
});
}
getAuthorizationUrl({ url }: ServerRequest, auth: Auth, state: string, nonce: string) {
getAuthorizationUrl({ url }: RequestEvent, auth: Auth, state: string, nonce: string) {
const data = {
state,
nonce,

View File

@ -1,5 +1,4 @@
import { OAuth2Provider, OAuth2ProviderConfig } from "./oauth2";
import type { ProfileCallback } from "./oauth2.base";
export interface RedditProfile {
is_employee: boolean;

View File

@ -1,6 +1,5 @@
import { ServerRequest } from "@sveltejs/kit/types/hooks";
import { RequestEvent } from "@sveltejs/kit/types/hooks";
import type { Auth } from "../auth";
import type { CallbackResult } from "../types";
import { OAuth2BaseProvider, OAuth2BaseProviderConfig } from "./oauth2.base";
interface TwitterAuthProviderConfig extends OAuth2BaseProviderConfig {
@ -38,7 +37,7 @@ export class TwitterAuthProvider extends OAuth2BaseProvider<any, any, TwitterAut
};
}
async getAuthorizationUrl({ url }: ServerRequest, auth: Auth, state: string, nonce: string) {
async getAuthorizationUrl({ url }: RequestEvent, auth: Auth, state: string, nonce: string) {
const endpoint = "https://api.twitter.com/oauth/authorize";
const { oauthToken } = await this.getRequestToken(auth, url.host);
@ -71,7 +70,8 @@ export class TwitterAuthProvider extends OAuth2BaseProvider<any, any, TwitterAut
return await res.json();
}
async callback({ url }: ServerRequest, auth: Auth): Promise<CallbackResult> {
async callback(event: RequestEvent, auth: Auth): Promise<any> {
const { url } = event;
const oauthToken = url.searchParams.get("oauth_token");
const oauthVerifier = url.searchParams.get("oauth_verifier");
const redirect = this.getStateValue(url.searchParams, "redirect");

2331
yarn.lock

File diff suppressed because it is too large Load Diff