Skip to content

Commit

Permalink
feat(auth): replace custom auth with next-auth
Browse files Browse the repository at this point in the history
  • Loading branch information
oleast committed Jan 24, 2021
1 parent e45f34f commit 90d028c
Show file tree
Hide file tree
Showing 29 changed files with 859 additions and 579 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jobs:
env:
NEXT_PUBLIC_BUILD_TIME_COURSE_LIMIT: ${{ secrets.NEXT_PUBLIC_BUILD_TIME_COURSE_LIMIT }}
NEXT_PUBLIC_GRADES_API_URL: ${{ secrets.NEXT_PUBLIC_GRADES_API_URL }}
NEXT_PUBLIC_CANONICAL_URL: ${{ secrets.NEXT_PUBLIC_CANONICAL_URL }}
NEXT_PUBLIC_SENTRY_DSN: ${{ secrets.NEXT_PUBLIC_SENTRY_DSN }}
NEXT_PUBLIC_GA_TRACKING_ID: ${{ secrets.NEXT_PUBLIC_GA_TRACKING_ID }}
NEXT_PUBLIC_FEIDE_CLIENT_ID: ${{ secrets.NEXT_PUBLIC_FEIDE_CLIENT_ID }}
Expand All @@ -44,4 +45,4 @@ jobs:
password: ${{ secrets.DOCKER_PASSWORD }}
registry: registry.online.ntnu.no
tags: 'latest,${{ steps.version.outputs.tag }}'
buildargs: NEXT_PUBLIC_BUILD_TIME_COURSE_LIMIT,NEXT_PUBLIC_GRADES_API_URL,NEXT_PUBLIC_SENTRY_DSN,NEXT_PUBLIC_GA_TRACKING_ID,NEXT_PUBLIC_FEIDE_CLIENT_ID,NEXT_PUBLIC_FEIDE_AUTH_ENDPOINT
buildargs: NEXT_PUBLIC_BUILD_TIME_COURSE_LIMIT,NEXT_PUBLIC_GRADES_API_URL,NEXT_PUBLIC_CANONICAL_URL,NEXT_PUBLIC_SENTRY_DSN,NEXT_PUBLIC_GA_TRACKING_ID,NEXT_PUBLIC_FEIDE_CLIENT_ID,NEXT_PUBLIC_FEIDE_AUTH_ENDPOINT
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ FROM node:14-slim AS builder
# Public variables are only required at build time.
ARG NEXT_PUBLIC_BUILD_TIME_COURSE_LIMIT
ARG NEXT_PUBLIC_GRADES_API_URL
ARG NEXT_PUBLIC_CANONICAL_URL
ARG NEXT_PUBLIC_SENTRY_DSN
ARG NEXT_PUBLIC_GA_TRACKING_ID
ARG NEXT_PUBLIC_FEIDE_CLIENT_ID
Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ Variables have to be added in a lot of places to make everything work correctly!

Private variables have to be set in the runtime environment to be kept private.


### List of variables

```bash
Expand All @@ -47,13 +46,13 @@ NEXT_PUBLIC_SENTRY_DSN # Default = undefined
# Google analytics tracking ID
NEXT_PUBLIC_GA_TRACKING_ID # Default = __REPLACE_ME__
# Actual URL of the hosted project. Important for linking. for prod it should be https://grades.no
NEXT_PRIVATE_CANONICAL_URL # Default = http://localhost:3000
NEXT_PUBLIC_CANONICAL_URL # Default = http://localhost:3000
# Redirect url for next auth
NEXTAUTH_URL #
# Client ID for dataporten/FEIDE client for OIDC
NEXT_PUBLIC_FEIDE_CLIENT_ID # Default = undefined
# Client Secret for dataporten/FEIDE client for OIDC. HAS TO BE KEPT SECRET
NEXT_PRIVATE_FEIDE_CLIENT_SECRET # Default = undefined
# Base url for OIDC authentication (for auto discovery)
NEXT_PUBLIC_FEIDE_AUTH_ENDPOINT # Default = https://auth.dataporten.no
# Secret for encryting cookie for session. HAS TO BE KEPT SECRET
NEXT_PRIVATE_SESSION_SECRET # Default = "some_random_string"
```
7 changes: 2 additions & 5 deletions common/api/karstat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,14 @@ interface Data {
semester: string;
}

export const requestKarstatScrapeGradeReport = async (
{ username, password, departmentId, year, semester }: Data,
accessToken: string
) => {
export const requestKarstatScrapeGradeReport = async ({ username, password, departmentId, year, semester }: Data) => {
const data = {
username,
password,
department: departmentId,
year,
semester,
};
const response = await poster(getKarstatScrapeGradeReportUrl(), data, accessToken);
const response = await poster(getKarstatScrapeGradeReportUrl(), data);
return response;
};
4 changes: 2 additions & 2 deletions common/api/tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ interface Data {
courseCode: string;
}

export const requestCreateCourseTag = async ({ name, courseCode }: Data, accessToken: string) => {
export const requestCreateCourseTag = async ({ name, courseCode }: Data) => {
const data = {
name,
course: courseCode,
};
const response = await poster(getCourseTagListApiUrl(courseCode), data, accessToken);
const response = await poster(getCourseTagListApiUrl(courseCode), data);
return response;
};
4 changes: 2 additions & 2 deletions common/api/tia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ interface Data {
skip: number;
}

export const requestTIAScrapeCourses = async ({ username, password, limit, skip }: Data, accessToken: string) => {
export const requestTIAScrapeCourses = async ({ username, password, limit, skip }: Data) => {
const data = {
username,
password,
limit,
skip,
};
const response = await poster(getTIAScrapeCoursesUrl(), data, accessToken);
const response = await poster(getTIAScrapeCoursesUrl(), data);
return response;
};
59 changes: 0 additions & 59 deletions common/auth/middleware.js

This file was deleted.

63 changes: 0 additions & 63 deletions common/auth/passport.js

This file was deleted.

32 changes: 0 additions & 32 deletions common/auth/session.js

This file was deleted.

6 changes: 2 additions & 4 deletions common/auth/ssr.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getUserServer } from './utils';
import type { LoggedInUser } from 'models/User';
import type { GetServerSideProps, GetServerSidePropsContext, GetServerSidePropsResult } from 'next';
import type { ParsedUrlQuery } from 'querystring';
import { ssrAuthMiddleware } from 'common/auth/middleware';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type AnyProps = { [key: string]: any };
Expand All @@ -14,9 +14,7 @@ export const withUser = <Props extends AnyProps = AnyProps, Query extends Parsed
redirect = true
): GetServerSideProps<Props, Query> => {
const wrapper: GetServerSideProps<Props, Query> = async (ctx) => {
await ssrAuthMiddleware(ctx);
const request = ctx.req as typeof ctx.req & { user?: LoggedInUser };
const user = request.user || null;
const user = await getUserServer(ctx);
const result = await wrappedGetServerSideProps(ctx, user);
if (redirect && !user) {
return {
Expand Down
35 changes: 35 additions & 0 deletions common/auth/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { LoggedInUser } from 'models/User';
import { GetServerSidePropsContext } from 'next';
import { signIn, signOut, getSession } from 'next-auth/client';

export interface FeideProfile {
sub: string;
'connect-userid_sec': string[];
'dataporten-userid_sec': string[];
name: string;
email: string;
email_verified: boolean;
picture: string;
}

export const OIDC_CLIENT_NAME = 'feide';

export const logIn = async () => {
await signIn(OIDC_CLIENT_NAME);
};

export const logOut = async () => {
await signOut();
};

export const getUserClient = async () => {
const session = await getSession();
const user = (session as any) as LoggedInUser | null;
return user;
};

export const getUserServer = async (ctx: GetServerSidePropsContext) => {
const session = await getSession(ctx);
const user = (session as any) as LoggedInUser | null;
return user;
};
4 changes: 1 addition & 3 deletions common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ export const BUILD_TIME_COURSE_LIMIT = Number(process.env.NEXT_PUBLIC_BUILD_TIME
export const GRADES_API_URL = process.env.NEXT_PUBLIC_GRADES_API_URL || 'https://grades.no';
export const SENTRY_DSN = process.env.NEXT_PUBLIC_SENTRY_DSN || null;
export const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GA_TRACKING_ID || '__REPLACE_ME__';
export const CANONICAL_URL = process.env.NEXT_PRIVATE_CANONICAL_URL || "http://localhost:3000";
export const CANONICAL_URL = process.env.NEXT_PUBLIC_CANONICAL_URL || "http://localhost:3000";
export const FEIDE_CLIENT_ID = process.env.NEXT_PUBLIC_FEIDE_CLIENT_ID;
export const FEIDE_CLIENT_SECRET = process.env.NEXT_PRIVATE_FEIDE_CLIENT_SECRET;
export const FEIDE_AUTH_ENDPOINT = process.env.NEXT_PUBLIC_FEIDE_AUTH_ENDPOINT || 'https://auth.dataporten.no';
export const SESSION_SECRET =
process.env.NEXT_PRIVATE_SESSION_SECRET || 'some_not_random_password_that_is_at_least_32_characters';
export const GITHUB_TOKEN = process.env.NEXT_PRIVATE_GITHUB_TOKEN || undefined;
32 changes: 22 additions & 10 deletions common/fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@
export const fetcher = async <T>(url: string) => {
// hashtags will hopefully never be used in a server call
const uri = encodeURI(url).replace('#', '#');
const response = await fetch(uri);
const data = await response.json();
return data as T;
};
import { getUserClient } from './auth/utils';

export interface ListResponse<Data> {
count: number;
results: Data[];
}

export const poster = async <Data>(url: string, data: Data, accessToken?: string) => {
const getHeaders = async () => {
const user = await getUserClient();
const headers = new Headers({
'Content-Type': 'application/json',
});
if (accessToken) {
headers.set("Authorization", `Bearer ${accessToken}`);
if (user?.accessToken) {
headers.set("Authorization", `Bearer ${user.accessToken}`);
}
return headers;
};

export const fetcher = async <T>(url: string) => {
// hashtags will hopefully never be used in a server call
const uri = encodeURI(url).replace('#', '#');
const headers = await getHeaders();
const response = await fetch(uri, {
method: 'GET',
headers,
});
const data = await response.json();
return data as T;
};

export const poster = async <Data>(url: string, data: Data) => {
const headers = await getHeaders();
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(data),
Expand Down
12 changes: 4 additions & 8 deletions common/hooks/useUser.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import { LoggedInUser } from 'models/User';
import useSWR from 'swr';

import { fetcher } from '../fetcher';
import { useSession } from 'next-auth/client';

export const useUser = () => {
const { data, mutate, revalidate } = useSWR<{ user: LoggedInUser }>('/api/auth/user', fetcher);
const loading = !data;
const user = data?.user;
const functions = { mutate, loading, revalidate };
return [user, functions] as [typeof user, typeof functions];
const [session, loading] = useSession();
const user = (session as any) as LoggedInUser | null;
return [user, loading] as [typeof user, typeof loading];
};
6 changes: 6 additions & 0 deletions common/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ export const getCourseTagListApiUrl = (courseCode: string, { limit = 50, offset
export const getCourseTagDetailApiUrl = (courseCode: string, tagName: string) => {
return `${GRADES_API_URL}/api/v2/courses/${courseCode}/tags/${tagName}/`;
};
export const getUserListApiUrl = ({ limit = 50, offset = 0 }: ListParams = {}) => {
return `${GRADES_API_URL}/api/v2/users/?limit=${limit}&offset=${offset}`;
};
export const getUserDetailApiUrl = (username: string) => {
return `${GRADES_API_URL}/api/v2/users/${username}/`;
};
export const getReportListApiUrl = ({ limit = 50, offset = 0 }: ListParams = {}) => {
return `${GRADES_API_URL}/api/v2/reports/?limit=${limit}&offset=${offset}`;
};
Expand Down
Loading

0 comments on commit 90d028c

Please sign in to comment.