diff --git a/backend/src/routes/website-api/errors.ts b/backend/src/routes/website-api/errors.ts index 5dcef63..f9256c9 100644 --- a/backend/src/routes/website-api/errors.ts +++ b/backend/src/routes/website-api/errors.ts @@ -1,3 +1,5 @@ +import { ApolloError } from 'apollo-server-fastify'; + export class UnknownPromptError extends Error { public name = 'UnknownPromptError'; @@ -6,10 +8,10 @@ export class UnknownPromptError extends Error { } } -export class InvalidCredentialsError extends Error { - public name = 'InvalidCredentialsError'; +export class InvalidVulcanCredentialsError extends ApolloError { + public name = 'InvalidVulcanCredentialsError'; public constructor() { - super('User with provided credentials not found'); + super('Invalid vulcan credentials', 'INVALID_VULCAN_CREDENTIALS'); } } diff --git a/backend/src/routes/website-api/resolvers/login-resolver.ts b/backend/src/routes/website-api/resolvers/login-resolver.ts index 2b2ff0b..8a2b4cc 100644 --- a/backend/src/routes/website-api/resolvers/login-resolver.ts +++ b/backend/src/routes/website-api/resolvers/login-resolver.ts @@ -6,9 +6,9 @@ import { Arg, Ctx, Mutation, Resolver, } from 'type-graphql'; import { - encryptSymmetrical, encryptWithPublicKey, generatePrivatePublicPair, requireEnvHex, + encryptSymmetrical, encryptWithPublicKey, generatePrivatePublicPair, isObject, requireEnvHex, } from '../../../utils'; -import { UnknownPromptError } from '../errors'; +import { InvalidVulcanCredentialsError, UnknownPromptError } from '../errors'; import LoginResult from '../models/login-result'; import type LoginResultStudent from '../models/login-result-student'; import type { WebsiteAPIContext } from '../types'; @@ -29,7 +29,12 @@ export default class LoginResolver { username, password, })); - await client.login(); + try { + await client.login(); + } catch (error) { + if (isObject(error) && error.name === 'InvalidCredentialsError') throw new InvalidVulcanCredentialsError(); + throw error; + } const diaryList = await client.getDiaryList(); const { privateKey, publicKey } = await generatePrivatePublicPair(); const encryptedPrivateKey = encryptSymmetrical( diff --git a/website/codegen.yml b/website/codegen.yml index 1ae4473..a4d6b29 100644 --- a/website/codegen.yml +++ b/website/codegen.yml @@ -1,6 +1,7 @@ schema: http://localhost:3000/api/website/graphql documents: - src/graphql/queries/get-prompt-info.ts + - src/graphql/mutations/login.ts generates: ./src/graphql/generated.ts: plugins: diff --git a/website/src/compontents/authenticate-prompt-windows/login-window.vue b/website/src/compontents/authenticate-prompt-windows/login-window.vue new file mode 100644 index 0000000..7cce912 --- /dev/null +++ b/website/src/compontents/authenticate-prompt-windows/login-window.vue @@ -0,0 +1,101 @@ + + + diff --git a/website/src/compontents/authenticate-prompt-windows/overview-window.vue b/website/src/compontents/authenticate-prompt-windows/overview-window.vue new file mode 100644 index 0000000..f45bd98 --- /dev/null +++ b/website/src/compontents/authenticate-prompt-windows/overview-window.vue @@ -0,0 +1,102 @@ + + + diff --git a/website/src/graphql/generated.ts b/website/src/graphql/generated.ts index 2c81eb1..8a5785e 100644 --- a/website/src/graphql/generated.ts +++ b/website/src/graphql/generated.ts @@ -71,6 +71,25 @@ export type LoginResultStudent = { name: Scalars['String']; }; +export type LoginMutationVariables = Exact<{ + promptId: Scalars['String']; + host: Scalars['String']; + username: Scalars['String']; + password: Scalars['String']; +}>; + +export type LoginMutation = ( + { __typename?: 'Mutation' } + & { login: ( + { __typename?: 'LoginResult' } + & Pick + & { students: Array<( + { __typename?: 'LoginResultStudent' } + & Pick + )>; } + ); } +); + export type GetPromptInfoQueryVariables = Exact<{ promptId: Scalars['String']; }>; @@ -79,7 +98,7 @@ export type GetPromptInfoQuery = ( { __typename?: 'Query' } & { promptInfo: ( { __typename?: 'PromptInfo' } - & Pick + & Pick & { application: ( { __typename?: 'PromptInfoApplication' } & Pick @@ -87,9 +106,26 @@ export type GetPromptInfoQuery = ( ); } ); +export const LoginDocument = gql` + mutation Login($promptId: String!, $host: String!, $username: String!, $password: String!) { + login( + host: $host + password: $password + username: $username + promptId: $promptId + ) { + encryptedPrivateKey + students { + studentId + name + } + } +} + `; export const GetPromptInfoDocument = gql` query GetPromptInfo($promptId: String!) { promptInfo(promptId: $promptId) { + id scopes studentsMode application { @@ -107,6 +143,9 @@ export type SdkFunctionWrapper = (action: () => Promise) => Promise; const defaultWrapper: SdkFunctionWrapper = (sdkFunction) => sdkFunction(); export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = defaultWrapper) { return { + Login(variables: LoginMutationVariables, requestHeaders?: Headers): Promise { + return withWrapper(() => client.request(print(LoginDocument), variables, requestHeaders)); + }, GetPromptInfo(variables: GetPromptInfoQueryVariables, requestHeaders?: Headers): Promise { return withWrapper(() => client.request(print(GetPromptInfoDocument), variables, requestHeaders)); }, diff --git a/website/src/graphql/mutations/login.ts b/website/src/graphql/mutations/login.ts new file mode 100644 index 0000000..e275bee --- /dev/null +++ b/website/src/graphql/mutations/login.ts @@ -0,0 +1,12 @@ +import gql from 'graphql-tag'; + +export default gql`mutation Login($promptId: String!, $host: String!, $username: String!, $password: String!) { + login(host: $host, password: $password, username: $username, promptId: $promptId) { + encryptedPrivateKey + students { + studentId + name + } + } +} +`; diff --git a/website/src/graphql/queries/get-prompt-info.ts b/website/src/graphql/queries/get-prompt-info.ts index f78a878..4234e49 100644 --- a/website/src/graphql/queries/get-prompt-info.ts +++ b/website/src/graphql/queries/get-prompt-info.ts @@ -2,6 +2,7 @@ import gql from 'graphql-tag'; export default gql`query GetPromptInfo($promptId: String!) { promptInfo(promptId: $promptId) { + id scopes studentsMode application { @@ -11,4 +12,5 @@ export default gql`query GetPromptInfo($promptId: String!) { verified } } -}`; +} +`; diff --git a/website/src/pages/authenticate-prompt/app.vue b/website/src/pages/authenticate-prompt/app.vue index 1990459..6eb9c5b 100644 --- a/website/src/pages/authenticate-prompt/app.vue +++ b/website/src/pages/authenticate-prompt/app.vue @@ -69,62 +69,13 @@ -
-
-

- Aplikacja - {{ promptInfo.application.name }} - chce uzyskać dostęp do twojego konta VULCAN UONET+ przez Wulkanowy Bridge -

- Uprawnienia aplikacji - - - - {{ item.icon }} - - - - {{ item.title }} - - - {{ item.subtitle }} - - - - - - {{ promptInfo.application.name }} - nie zobaczy twojego hasła - - - -
- - - Odmów - - - - Dalej - - -
+
- -
- - - Wróć - - - - Zaloguj się - - -
+ +
@@ -162,31 +113,19 @@ diff --git a/website/src/pages/authenticate-prompt/sdk.ts b/website/src/pages/authenticate-prompt/sdk.ts new file mode 100644 index 0000000..f18e380 --- /dev/null +++ b/website/src/pages/authenticate-prompt/sdk.ts @@ -0,0 +1,18 @@ +import { ClientError, GraphQLClient } from 'graphql-request'; +import { getSdk } from '@/graphql/generated'; +import { GraphQLError } from 'graphql-request/dist/types'; + +const client = new GraphQLClient('/api/website/graphql'); +export const sdk = getSdk(client); + +export interface GraphQLErrorFull extends GraphQLError { + extensions?: { + code?: string; + }; +} + +export function hasErrorCode(error: unknown, code: string): boolean { + if (!(error instanceof ClientError)) return false; + return error.response.errors + ?.some((resError: GraphQLErrorFull) => resError.extensions?.code === code) ?? false; +} diff --git a/website/src/types.ts b/website/src/types.ts new file mode 100644 index 0000000..acf5962 --- /dev/null +++ b/website/src/types.ts @@ -0,0 +1,17 @@ +export enum StudentsMode { + None = 'none', + One = 'one', + Many = 'many', +} + +export interface PromptInfo { + id: string; + scopes: string[]; + studentsMode: StudentsMode; + application: { + name: string; + iconUrl: string | null; + iconColor: string; + verified: boolean; + }; +}