@vue-skuilder/common-ui 0.2.20 → 0.2.22
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.
- package/dist/assets/index.css +1 -1
- package/dist/common-ui.es.js +152 -101
- package/dist/common-ui.es.js.map +1 -1
- package/dist/common-ui.umd.js +7 -7
- package/dist/common-ui.umd.js.map +1 -1
- package/dist/components/StudySession.types.d.ts +7 -1
- package/dist/components/StudySession.types.d.ts.map +1 -1
- package/dist/components/StudySession.vue.d.ts.map +1 -1
- package/dist/components/auth/UserLogin.vue.d.ts.map +1 -1
- package/dist/components/auth/UserRegistration.vue.d.ts +8 -0
- package/dist/components/auth/UserRegistration.vue.d.ts.map +1 -1
- package/dist/components/auth/index.d.ts +1 -1
- package/dist/components/auth/index.d.ts.map +1 -1
- package/dist/services/authAPI.d.ts +20 -0
- package/dist/services/authAPI.d.ts.map +1 -1
- package/dist/stores/useAuthStore.d.ts +8 -0
- package/dist/stores/useAuthStore.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/components/StudySession.types.ts +8 -1
- package/src/components/StudySession.vue +26 -0
- package/src/components/auth/UserLogin.vue +44 -8
- package/src/components/auth/UserRegistration.vue +3 -4
- package/src/components/auth/index.ts +2 -0
- package/src/services/authAPI.ts +66 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReplanHints, OutcomeObserver } from '../../../db/dist/index.mjs';
|
|
1
|
+
import { ReplanHints, OutcomeObserver, SessionStateSnapshotProvider } from '../../../db/dist/index.mjs';
|
|
2
2
|
/**
|
|
3
3
|
* Misc. config for a study StudySessionConfig
|
|
4
4
|
*/
|
|
@@ -40,6 +40,12 @@ export type StudySessionConfig = {
|
|
|
40
40
|
* session hints, request a replan). See `OutcomeObserver` in `@vue-skuilder/db`.
|
|
41
41
|
*/
|
|
42
42
|
outcomeObservers?: OutcomeObserver[];
|
|
43
|
+
/**
|
|
44
|
+
* Thin learner-state snapshot taken at session open and close, stored on the
|
|
45
|
+
* session's `StudySessionDoc`. Keep the returned object small — counts, ids,
|
|
46
|
+
* scores. Time-boxed and error-isolated by the controller.
|
|
47
|
+
*/
|
|
48
|
+
sessionStateSnapshot?: SessionStateSnapshotProvider;
|
|
43
49
|
};
|
|
44
50
|
/**
|
|
45
51
|
* Built-in card transition presets provided by StudySession.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StudySession.types.d.ts","sourceRoot":"","sources":["../../src/components/StudySession.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"StudySession.types.d.ts","sourceRoot":"","sources":["../../src/components/StudySession.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAC;AAEnG;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,aAAa,EAAE,OAAO,CAAC;IAEvB;;;;;;;;;;;;OAYG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,WAAW,CAAC;IAExB;;;;;;;;;OASG;IACH,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;IAErC;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,4BAA4B,CAAC;CACrD,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG,YAAY,GAAG,YAAY,CAAC;AAElF,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StudySession.vue.d.ts","sourceRoot":"","sources":["../../src/components/StudySession.vue"],"names":[],"mappings":"AAiFA;AAAA,
|
|
1
|
+
{"version":3,"file":"StudySession.vue.d.ts","sourceRoot":"","sources":["../../src/components/StudySession.vue"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserLogin.vue.d.ts","sourceRoot":"","sources":["../../../src/components/auth/UserLogin.vue"],"names":[],"mappings":"AA2DA;
|
|
1
|
+
{"version":3,"file":"UserLogin.vue.d.ts","sourceRoot":"","sources":["../../../src/components/auth/UserLogin.vue"],"names":[],"mappings":"AA2DA;AA2OA,UAAU,KAAK;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAqJD,iBAAS,cAAc;WAwPT,OAAO,IAA6B;;mCAVV,GAAG;;;;EAe1C;AAsBD,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AAC9D,QAAA,MAAM,eAAe;;;;;;;;;gBA1aN,MAAM;6EAmbnB,CAAC;wBACkB,uBAAuB,CAAC,OAAO,eAAe,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAAnG,wBAAoG;AAapG,KAAK,uBAAuB,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IACxC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KAEV,CAAA;CACD,CAAC"}
|
|
@@ -44,6 +44,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
|
|
|
44
44
|
getCourseRegDoc: (courseId: string) => Promise<import('../../../../db/dist/index.mjs').CourseRegistration>;
|
|
45
45
|
getActiveCourses: () => Promise<import('../../../../db/dist/index.mjs').CourseRegistration[]>;
|
|
46
46
|
getPendingReviews: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
47
|
+
getStudySessions: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').StudySessionDoc[]>;
|
|
47
48
|
getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
48
49
|
getActivityRecords: () => Promise<import('../../../../db/dist/index.mjs').ActivityRecord[]>;
|
|
49
50
|
getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
|
|
@@ -73,6 +74,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
|
|
|
73
74
|
putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
|
|
74
75
|
deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
|
|
75
76
|
putUserOutcome: (record: import('../../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
|
|
77
|
+
putStudySession: (doc: import('../../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
|
|
76
78
|
update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
|
|
77
79
|
createAccount: (username: string, password: string) => Promise<{
|
|
78
80
|
status: Status;
|
|
@@ -109,6 +111,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
|
|
|
109
111
|
getCourseRegDoc: (courseId: string) => Promise<import('../../../../db/dist/index.mjs').CourseRegistration>;
|
|
110
112
|
getActiveCourses: () => Promise<import('../../../../db/dist/index.mjs').CourseRegistration[]>;
|
|
111
113
|
getPendingReviews: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
114
|
+
getStudySessions: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').StudySessionDoc[]>;
|
|
112
115
|
getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
113
116
|
getActivityRecords: () => Promise<import('../../../../db/dist/index.mjs').ActivityRecord[]>;
|
|
114
117
|
getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
|
|
@@ -138,6 +141,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
|
|
|
138
141
|
putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
|
|
139
142
|
deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
|
|
140
143
|
putUserOutcome: (record: import('../../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
|
|
144
|
+
putStudySession: (doc: import('../../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
|
|
141
145
|
update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
|
|
142
146
|
createAccount: (username: string, password: string) => Promise<{
|
|
143
147
|
status: Status;
|
|
@@ -174,6 +178,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
|
|
|
174
178
|
getCourseRegDoc: (courseId: string) => Promise<import('../../../../db/dist/index.mjs').CourseRegistration>;
|
|
175
179
|
getActiveCourses: () => Promise<import('../../../../db/dist/index.mjs').CourseRegistration[]>;
|
|
176
180
|
getPendingReviews: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
181
|
+
getStudySessions: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').StudySessionDoc[]>;
|
|
177
182
|
getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
178
183
|
getActivityRecords: () => Promise<import('../../../../db/dist/index.mjs').ActivityRecord[]>;
|
|
179
184
|
getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
|
|
@@ -203,6 +208,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
|
|
|
203
208
|
putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
|
|
204
209
|
deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
|
|
205
210
|
putUserOutcome: (record: import('../../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
|
|
211
|
+
putStudySession: (doc: import('../../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
|
|
206
212
|
update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
|
|
207
213
|
createAccount: (username: string, password: string) => Promise<{
|
|
208
214
|
status: Status;
|
|
@@ -240,6 +246,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
|
|
|
240
246
|
getCourseRegDoc: (courseId: string) => Promise<import('../../../../db/dist/index.mjs').CourseRegistration>;
|
|
241
247
|
getActiveCourses: () => Promise<import('../../../../db/dist/index.mjs').CourseRegistration[]>;
|
|
242
248
|
getPendingReviews: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
249
|
+
getStudySessions: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').StudySessionDoc[]>;
|
|
243
250
|
getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
244
251
|
getActivityRecords: () => Promise<import('../../../../db/dist/index.mjs').ActivityRecord[]>;
|
|
245
252
|
getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
|
|
@@ -269,6 +276,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
|
|
|
269
276
|
putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
|
|
270
277
|
deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
|
|
271
278
|
putUserOutcome: (record: import('../../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
|
|
279
|
+
putStudySession: (doc: import('../../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
|
|
272
280
|
update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
|
|
273
281
|
createAccount: (username: string, password: string) => Promise<{
|
|
274
282
|
status: Status;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserRegistration.vue.d.ts","sourceRoot":"","sources":["../../../src/components/auth/UserRegistration.vue"],"names":[],"mappings":"AA8EA;AAAA,
|
|
1
|
+
{"version":3,"file":"UserRegistration.vue.d.ts","sourceRoot":"","sources":["../../../src/components/auth/UserRegistration.vue"],"names":[],"mappings":"AA8EA;AAAA,OAgSO,EAAmB,QAAQ,EAAE,MAAM,KAAK,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,MAAM,EAAO,MAAM,sBAAsB,CAAC;;;cAU3B,QAAQ,CAAC,CAAC,OAAO,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAA;SAAE,KAAK,IAAI,CAAC;;;;;;;;;;;;;;;;;;UAuBrD,eAAe,GAAG,IAAI;WACO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA8U9C,CAAC;;;;;;;;;;;;;wBAyC+D,CAAC;yBAC3D,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA1CN,CAAC;;;;;;;;;;;;;wBAyC+D,CAAC;yBAC3D,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA1CN,CAAC;;;;;;;;;;;;;wBAyC+D,CAAC;yBAC3D,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA1CN,CAAC;;;;;;;;;;;;;wBAyC+D,CAAC;yBAC3D,CAAA;;;;;;;;;;;;;;;;;yBA/WQ,OAAO;;;;;qBASX,MAAM;2BAGA,MAAM;;;;;;;;cA7CT,QAAQ,CAAC,CAAC,OAAO,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAA;SAAE,KAAK,IAAI,CAAC;;;;;;;AALzE,wBAoMG"}
|
|
@@ -5,5 +5,5 @@ export { default as UserLoginAndRegistrationContainer } from './UserLoginAndRegi
|
|
|
5
5
|
export { default as VerifyEmail } from './VerifyEmail.vue';
|
|
6
6
|
export { default as RequestPasswordReset } from './RequestPasswordReset.vue';
|
|
7
7
|
export { default as ResetPassword } from './ResetPassword.vue';
|
|
8
|
-
export { sendVerificationEmail, verifyEmail, getUserStatus, requestPasswordReset, resetPassword, type AuthResponse, type VerifyEmailResponse, type UserStatusResponse, } from '../../services/authAPI';
|
|
8
|
+
export { sendVerificationEmail, verifyEmail, getUserStatus, resolveLoginIdentifier, requestPasswordReset, resetPassword, type AuthResponse, type VerifyEmailResponse, type UserStatusResponse, type ResolveLoginResponse, } from '../../services/authAPI';
|
|
9
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,iCAAiC,EAAE,MAAM,yCAAyC,CAAC;AAGvG,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAC7E,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAG/D,OAAO,EACL,qBAAqB,EACrB,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,aAAa,EACb,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,iCAAiC,EAAE,MAAM,yCAAyC,CAAC;AAGvG,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAC7E,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAG/D,OAAO,EACL,qBAAqB,EACrB,WAAW,EACX,aAAa,EACb,sBAAsB,EACtB,oBAAoB,EACpB,aAAa,EACb,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,GAC1B,MAAM,wBAAwB,CAAC"}
|
|
@@ -30,6 +30,19 @@ export interface UserStatusResponse extends AuthResponse {
|
|
|
30
30
|
email?: string | null;
|
|
31
31
|
entitlements?: UserEntitlements;
|
|
32
32
|
}
|
|
33
|
+
export interface ResolveLoginResponse extends AuthResponse {
|
|
34
|
+
username?: string;
|
|
35
|
+
/**
|
|
36
|
+
* HTTP status of the resolve call on failure, so callers can distinguish a
|
|
37
|
+
* genuine credential rejection from a system/service failure. The server
|
|
38
|
+
* returns a uniform 401 for every real auth failure (unknown identifier,
|
|
39
|
+
* unverified email, wrong password — no enumeration leak); anything else
|
|
40
|
+
* (404 = route not deployed, 5xx, or `undefined` for a network error) means
|
|
41
|
+
* the resolver itself is unavailable, not that the credentials were wrong.
|
|
42
|
+
* Undefined on success.
|
|
43
|
+
*/
|
|
44
|
+
status?: number;
|
|
45
|
+
}
|
|
33
46
|
/**
|
|
34
47
|
* Triggers verification email send for a newly created account.
|
|
35
48
|
*
|
|
@@ -44,6 +57,13 @@ export declare function sendVerificationEmail(username: string, email: string, o
|
|
|
44
57
|
* Verify user email using token from magic link.
|
|
45
58
|
*/
|
|
46
59
|
export declare function verifyEmail(token: string): Promise<VerifyEmailResponse>;
|
|
60
|
+
/**
|
|
61
|
+
* Resolve a login identifier (username OR email) to a couch username, gated on
|
|
62
|
+
* the correct password. Returns the username only on success; every failure is
|
|
63
|
+
* a uniform "invalid credentials" (no account-enumeration leak). Used by the
|
|
64
|
+
* login form before calling user.login() when the identifier is an email.
|
|
65
|
+
*/
|
|
66
|
+
export declare function resolveLoginIdentifier(identifier: string, password: string): Promise<ResolveLoginResponse>;
|
|
47
67
|
/**
|
|
48
68
|
* Get current user's account status (verification status, email).
|
|
49
69
|
* Requires valid AuthSession cookie.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authAPI.d.ts","sourceRoot":"","sources":["../../src/services/authAPI.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,mBAAmB,CAAC,EAAE;YACpB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;SACxB,CAAC;KACH;CACF;AAgCD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAoB,SAAQ,YAAY;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAE3D,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,sBAAsB,GAAG,UAAU,GAAG,WAAW,CAAC;IAC3D,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,YAAY,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CA+BvB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,
|
|
1
|
+
{"version":3,"file":"authAPI.d.ts","sourceRoot":"","sources":["../../src/services/authAPI.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,mBAAmB,CAAC,EAAE;YACpB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;SACxB,CAAC;KACH;CACF;AAgCD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAoB,SAAQ,YAAY;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAE3D,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,sBAAsB,GAAG,UAAU,GAAG,WAAW,CAAC;IAC3D,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,YAAY,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAED,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CA+BvB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA+B7E;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,oBAAoB,CAAC,CAgC/B;AAED;;;GAGG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAsBjE;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CA4BvB;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,CAAC,CAuBvB"}
|
|
@@ -26,6 +26,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
|
|
|
26
26
|
getCourseRegDoc: (courseId: string) => Promise<import('../../../db/dist/index.mjs').CourseRegistration>;
|
|
27
27
|
getActiveCourses: () => Promise<import('../../../db/dist/index.mjs').CourseRegistration[]>;
|
|
28
28
|
getPendingReviews: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
29
|
+
getStudySessions: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').StudySessionDoc[]>;
|
|
29
30
|
getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
30
31
|
getActivityRecords: () => Promise<import('../../../db/dist/index.mjs').ActivityRecord[]>;
|
|
31
32
|
getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
|
|
@@ -55,6 +56,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
|
|
|
55
56
|
putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
|
|
56
57
|
deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
|
|
57
58
|
putUserOutcome: (record: import('../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
|
|
59
|
+
putStudySession: (doc: import('../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
|
|
58
60
|
update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
|
|
59
61
|
createAccount: (username: string, password: string) => Promise<{
|
|
60
62
|
status: import('../../../common/dist/index.mjs').Status;
|
|
@@ -91,6 +93,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
|
|
|
91
93
|
getCourseRegDoc: (courseId: string) => Promise<import('../../../db/dist/index.mjs').CourseRegistration>;
|
|
92
94
|
getActiveCourses: () => Promise<import('../../../db/dist/index.mjs').CourseRegistration[]>;
|
|
93
95
|
getPendingReviews: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
96
|
+
getStudySessions: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').StudySessionDoc[]>;
|
|
94
97
|
getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
95
98
|
getActivityRecords: () => Promise<import('../../../db/dist/index.mjs').ActivityRecord[]>;
|
|
96
99
|
getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
|
|
@@ -120,6 +123,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
|
|
|
120
123
|
putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
|
|
121
124
|
deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
|
|
122
125
|
putUserOutcome: (record: import('../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
|
|
126
|
+
putStudySession: (doc: import('../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
|
|
123
127
|
update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
|
|
124
128
|
createAccount: (username: string, password: string) => Promise<{
|
|
125
129
|
status: import('../../../common/dist/index.mjs').Status;
|
|
@@ -156,6 +160,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
|
|
|
156
160
|
getCourseRegDoc: (courseId: string) => Promise<import('../../../db/dist/index.mjs').CourseRegistration>;
|
|
157
161
|
getActiveCourses: () => Promise<import('../../../db/dist/index.mjs').CourseRegistration[]>;
|
|
158
162
|
getPendingReviews: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
163
|
+
getStudySessions: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').StudySessionDoc[]>;
|
|
159
164
|
getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
160
165
|
getActivityRecords: () => Promise<import('../../../db/dist/index.mjs').ActivityRecord[]>;
|
|
161
166
|
getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
|
|
@@ -185,6 +190,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
|
|
|
185
190
|
putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
|
|
186
191
|
deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
|
|
187
192
|
putUserOutcome: (record: import('../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
|
|
193
|
+
putStudySession: (doc: import('../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
|
|
188
194
|
update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
|
|
189
195
|
createAccount: (username: string, password: string) => Promise<{
|
|
190
196
|
status: import('../../../common/dist/index.mjs').Status;
|
|
@@ -222,6 +228,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
|
|
|
222
228
|
getCourseRegDoc: (courseId: string) => Promise<import('../../../db/dist/index.mjs').CourseRegistration>;
|
|
223
229
|
getActiveCourses: () => Promise<import('../../../db/dist/index.mjs').CourseRegistration[]>;
|
|
224
230
|
getPendingReviews: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
231
|
+
getStudySessions: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').StudySessionDoc[]>;
|
|
225
232
|
getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
226
233
|
getActivityRecords: () => Promise<import('../../../db/dist/index.mjs').ActivityRecord[]>;
|
|
227
234
|
getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
|
|
@@ -251,6 +258,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
|
|
|
251
258
|
putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
|
|
252
259
|
deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
|
|
253
260
|
putUserOutcome: (record: import('../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
|
|
261
|
+
putStudySession: (doc: import('../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
|
|
254
262
|
update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
|
|
255
263
|
createAccount: (username: string, password: string) => Promise<{
|
|
256
264
|
status: import('../../../common/dist/index.mjs').Status;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAuthStore.d.ts","sourceRoot":"","sources":["../../src/stores/useAuthStore.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGjE,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,eAAe,GAAG,SAAS,CAAC;IACnC,oBAAoB,EAAE;QACpB,IAAI,EAAE,OAAO,CAAC;QACd,QAAQ,EAAE,OAAO,CAAC;QAClB,aAAa,EAAE,OAAO,CAAC;QACvB,eAAe,EAAE,OAAO,CAAC;KAC1B,CAAC;IACF,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAiB3E;AAED,eAAO,MAAM,YAAY
|
|
1
|
+
{"version":3,"file":"useAuthStore.d.ts","sourceRoot":"","sources":["../../src/stores/useAuthStore.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGjE,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,eAAe,GAAG,SAAS,CAAC;IACnC,oBAAoB,EAAE;QACpB,IAAI,EAAE,OAAO,CAAC;QACd,QAAQ,EAAE,OAAO,CAAC;QAClB,aAAa,EAAE,OAAO,CAAC;QACvB,eAAe,EAAE,OAAO,CAAC;KAC1B,CAAC;IACF,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAiB3E;AAED,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAgFk9pB,CAAC;;;;;;;;;;;;;oBAAm7D,CAAC;qBAAuB,CAAC;;;;;;;kBA3G96tB,OAAO;sBACH,OAAO;2BACF,OAAO;6BACL,OAAO;;wBAEV,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAsGk9pB,CAAC;;;;;;;;;;;;;oBAAm7D,CAAC;qBAAuB,CAAC;;;;;;;kBA3G96tB,OAAO;sBACH,OAAO;2BACF,OAAO;6BACL,OAAO;;wBAEV,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAsGk9pB,CAAC;;;;;;;;;;;;;oBAAm7D,CAAC;qBAAuB,CAAC;;;;;;;kBA3G96tB,OAAO;sBACH,OAAO;2BACF,OAAO;6BACL,OAAO;;wBAEV,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAsGk9pB,CAAC;;;;;;;;;;;;;oBAAm7D,CAAC;qBAAuB,CAAC;;;;;;;;;yBA1C75tB,OAAO;uBAIT,OAAO;;;;;EAqC/B,CAAC"}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.22",
|
|
7
7
|
"sideEffects": false,
|
|
8
8
|
"main": "./dist/common-ui.umd.js",
|
|
9
9
|
"module": "./dist/common-ui.es.js",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"@highlightjs/vue-plugin": "^2.1.2",
|
|
35
35
|
"@mdi/font": "^7.3.67",
|
|
36
36
|
"@vojtechlanka/vue-tags-input": "^3",
|
|
37
|
-
"@vue-skuilder/common": "0.2.
|
|
38
|
-
"@vue-skuilder/db": "0.2.
|
|
37
|
+
"@vue-skuilder/common": "0.2.22",
|
|
38
|
+
"@vue-skuilder/db": "0.2.22",
|
|
39
39
|
"highlight.js": "^11.0.1",
|
|
40
40
|
"marked": "^15.0.6",
|
|
41
41
|
"moment": "^2.29.4",
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"vite-plugin-dts": "^4.5.3",
|
|
72
72
|
"vitest": "^4.1.0"
|
|
73
73
|
},
|
|
74
|
-
"stableVersion": "0.2.
|
|
74
|
+
"stableVersion": "0.2.22"
|
|
75
75
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ReplanHints, OutcomeObserver } from '@vue-skuilder/db';
|
|
1
|
+
import type { ReplanHints, OutcomeObserver, SessionStateSnapshotProvider } from '@vue-skuilder/db';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Misc. config for a study StudySessionConfig
|
|
@@ -44,6 +44,13 @@ export type StudySessionConfig = {
|
|
|
44
44
|
* session hints, request a replan). See `OutcomeObserver` in `@vue-skuilder/db`.
|
|
45
45
|
*/
|
|
46
46
|
outcomeObservers?: OutcomeObserver[];
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Thin learner-state snapshot taken at session open and close, stored on the
|
|
50
|
+
* session's `StudySessionDoc`. Keep the returned object small — counts, ids,
|
|
51
|
+
* scores. Time-boxed and error-isolated by the controller.
|
|
52
|
+
*/
|
|
53
|
+
sessionStateSnapshot?: SessionStateSnapshotProvider;
|
|
47
54
|
};
|
|
48
55
|
|
|
49
56
|
/**
|
|
@@ -113,6 +113,7 @@ import {
|
|
|
113
113
|
import confetti from 'canvas-confetti';
|
|
114
114
|
|
|
115
115
|
import { StudySessionConfig, CardTransitionPreset, CardTransitionMode } from './StudySession.types';
|
|
116
|
+
import type { SessionStateSnapshotProvider } from '@vue-skuilder/db';
|
|
116
117
|
|
|
117
118
|
interface StudyRefs {
|
|
118
119
|
shadowWrapper: HTMLDivElement;
|
|
@@ -273,6 +274,17 @@ export default defineComponent({
|
|
|
273
274
|
console.log('[StudySession] InitSession completed in created hook');
|
|
274
275
|
},
|
|
275
276
|
|
|
277
|
+
beforeUnmount() {
|
|
278
|
+
// Torn down mid-session (route change, parent v-if) = abandoned.
|
|
279
|
+
// `endSession` is idempotent, so a naturally-ended session keeps `closed`.
|
|
280
|
+
if (this.sessionController && !this.sessionFinished) {
|
|
281
|
+
void this.sessionController.endSession('abandoned');
|
|
282
|
+
}
|
|
283
|
+
if (this.intervalHandler) {
|
|
284
|
+
clearInterval(this.intervalHandler);
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
|
|
276
288
|
errorCaptured(err: unknown, _instance: unknown, info: string) {
|
|
277
289
|
console.error(`[StudySession] Card render error (${info}), skipping card "${this.cardID}":`, err);
|
|
278
290
|
if (this.sessionController) {
|
|
@@ -399,6 +411,8 @@ export default defineComponent({
|
|
|
399
411
|
const scOptions: {
|
|
400
412
|
defaultBatchLimit?: number;
|
|
401
413
|
outcomeObservers?: OutcomeObserver[];
|
|
414
|
+
courseId?: string;
|
|
415
|
+
stateSnapshotProvider?: SessionStateSnapshotProvider;
|
|
402
416
|
} = {};
|
|
403
417
|
if (this.sessionConfig?.defaultBatchLimit !== undefined) {
|
|
404
418
|
scOptions.defaultBatchLimit = this.sessionConfig.defaultBatchLimit;
|
|
@@ -406,6 +420,15 @@ export default defineComponent({
|
|
|
406
420
|
if (this.sessionConfig?.outcomeObservers?.length) {
|
|
407
421
|
scOptions.outcomeObservers = this.sessionConfig.outcomeObservers;
|
|
408
422
|
}
|
|
423
|
+
// A session can span several sources; the first course-type one owns
|
|
424
|
+
// the record. Absent it, the controller skips session recording.
|
|
425
|
+
const recordingCourseId = this.contentSources.find((s) => s.type === 'course')?.id;
|
|
426
|
+
if (recordingCourseId) {
|
|
427
|
+
scOptions.courseId = recordingCourseId;
|
|
428
|
+
}
|
|
429
|
+
if (this.sessionConfig?.sessionStateSnapshot) {
|
|
430
|
+
scOptions.stateSnapshotProvider = this.sessionConfig.sessionStateSnapshot;
|
|
431
|
+
}
|
|
409
432
|
|
|
410
433
|
this.sessionController = markRaw(
|
|
411
434
|
new SessionController<ViewComponent>(
|
|
@@ -487,6 +510,9 @@ export default defineComponent({
|
|
|
487
510
|
|
|
488
511
|
r.cardID = this.cardID;
|
|
489
512
|
r.courseID = this.courseID;
|
|
513
|
+
// The single chokepoint every response passes through en route to
|
|
514
|
+
// putCardRecord.
|
|
515
|
+
r.sessionId = this.sessionController?.sessionId;
|
|
490
516
|
this.currentCard.records.push(r);
|
|
491
517
|
|
|
492
518
|
console.log(`[StudySession] StudySession.processResponse is running...`);
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
<v-form onsubmit="return false;" @submit.prevent="login">
|
|
7
7
|
<v-text-field
|
|
8
8
|
id=""
|
|
9
|
-
v-model="
|
|
9
|
+
v-model="identifier"
|
|
10
10
|
autofocus
|
|
11
11
|
name="username"
|
|
12
|
-
label="Username"
|
|
12
|
+
label="Username or email"
|
|
13
13
|
prepend-icon="mdi-account-circle"
|
|
14
14
|
></v-text-field>
|
|
15
15
|
<v-text-field
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
></v-text-field>
|
|
27
27
|
|
|
28
28
|
<v-snackbar v-model="badLoginAttempt" location="bottom right" :timeout="errorTimeout">
|
|
29
|
-
|
|
29
|
+
{{ errorMessage }}
|
|
30
30
|
<v-btn color="pink" variant="text" @click="badLoginAttempt = false">Close</v-btn>
|
|
31
31
|
</v-snackbar>
|
|
32
32
|
|
|
@@ -67,6 +67,7 @@ import { Status } from '@vue-skuilder/common';
|
|
|
67
67
|
import { User } from '@vue-skuilder/db';
|
|
68
68
|
import { getCurrentUser, useAuthStore } from '../../stores/useAuthStore';
|
|
69
69
|
import { useConfigStore } from '../../stores/useConfigStore';
|
|
70
|
+
import { resolveLoginIdentifier } from '../../services/authAPI';
|
|
70
71
|
|
|
71
72
|
// Define props
|
|
72
73
|
interface Props {
|
|
@@ -89,12 +90,22 @@ const route = useRoute();
|
|
|
89
90
|
const authStore = useAuthStore();
|
|
90
91
|
const configStore = useConfigStore();
|
|
91
92
|
|
|
92
|
-
|
|
93
|
+
// Holds a username OR an email address (see login()).
|
|
94
|
+
const identifier = ref('');
|
|
93
95
|
const password = ref('');
|
|
94
96
|
const passwordVisible = ref(false);
|
|
95
97
|
const awaitingResponse = ref(false);
|
|
96
98
|
const badLoginAttempt = ref(false);
|
|
97
99
|
const errorTimeout = ref(7000);
|
|
100
|
+
const errorMessage = ref('Username or password was incorrect.');
|
|
101
|
+
|
|
102
|
+
// Shown when the email→username resolver is unreachable (route missing / 5xx /
|
|
103
|
+
// network) rather than when credentials are actually wrong. Telling the two
|
|
104
|
+
// apart matters: "wrong password" sends the user to re-check what is in fact
|
|
105
|
+
// correct, and hides a real backend outage.
|
|
106
|
+
const BAD_CREDENTIALS_MSG = 'Username or password was incorrect.';
|
|
107
|
+
const LOGIN_UNAVAILABLE_MSG =
|
|
108
|
+
'Login is temporarily unavailable. Please try again in a moment.';
|
|
98
109
|
const user = ref<User | undefined>(undefined);
|
|
99
110
|
|
|
100
111
|
const loginRoute = computed(() => route.name === 'login');
|
|
@@ -104,11 +115,12 @@ const buttonStatus = computed(() => ({
|
|
|
104
115
|
text: badLoginAttempt.value ? 'Try again' : 'Log In',
|
|
105
116
|
}));
|
|
106
117
|
|
|
107
|
-
const initBadLogin = () => {
|
|
118
|
+
const initBadLogin = (message: string = BAD_CREDENTIALS_MSG) => {
|
|
119
|
+
errorMessage.value = message;
|
|
108
120
|
badLoginAttempt.value = true;
|
|
109
121
|
|
|
110
122
|
alertUser({
|
|
111
|
-
text:
|
|
123
|
+
text: message,
|
|
112
124
|
status: Status.error,
|
|
113
125
|
timeout: errorTimeout.value,
|
|
114
126
|
});
|
|
@@ -120,7 +132,7 @@ const initBadLogin = () => {
|
|
|
120
132
|
const login = async () => {
|
|
121
133
|
awaitingResponse.value = true;
|
|
122
134
|
log('Starting login attempt');
|
|
123
|
-
log(`Login attempt for
|
|
135
|
+
log(`Login attempt for identifier: ${identifier.value}`);
|
|
124
136
|
|
|
125
137
|
try {
|
|
126
138
|
log('Attempting to get User instance');
|
|
@@ -128,7 +140,31 @@ const login = async () => {
|
|
|
128
140
|
user.value = await getCurrentUser();
|
|
129
141
|
log('Got User instance, attempting login');
|
|
130
142
|
|
|
131
|
-
|
|
143
|
+
// Couch _session authenticates by username only. When the identifier is an
|
|
144
|
+
// email, resolve it to the account username first — server-side and gated
|
|
145
|
+
// on the correct password, so it can't be used to enumerate accounts. A
|
|
146
|
+
// plain username is passed straight through.
|
|
147
|
+
let loginName = identifier.value;
|
|
148
|
+
if (identifier.value.includes('@')) {
|
|
149
|
+
const resolved = await resolveLoginIdentifier(identifier.value, password.value);
|
|
150
|
+
if (!resolved.ok || !resolved.username) {
|
|
151
|
+
// 401 is the server's uniform "real auth failure" code; anything else
|
|
152
|
+
// (404 = route not deployed, 5xx, or no status = network) means the
|
|
153
|
+
// resolver is unavailable, not that the credentials were wrong.
|
|
154
|
+
if (resolved.status === 401) {
|
|
155
|
+
log('Email identifier did not resolve: invalid credentials');
|
|
156
|
+
initBadLogin();
|
|
157
|
+
} else {
|
|
158
|
+
log(`Email login unavailable (resolver error: ${resolved.error ?? 'network'})`);
|
|
159
|
+
initBadLogin(LOGIN_UNAVAILABLE_MSG);
|
|
160
|
+
}
|
|
161
|
+
awaitingResponse.value = false;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
loginName = resolved.username;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
await user.value.login(loginName, password.value);
|
|
132
168
|
log('Login successful');
|
|
133
169
|
|
|
134
170
|
// load user config
|
|
@@ -209,12 +209,11 @@ Author: ${this.author}
|
|
|
209
209
|
this.authStore.loginAndRegistration.init = false;
|
|
210
210
|
this.authStore.loginAndRegistration.init = true;
|
|
211
211
|
|
|
212
|
-
//
|
|
212
|
+
// Send verification email if an address was provided. The address
|
|
213
|
+
// is persisted to the _users doc by send-verification (the single
|
|
214
|
+
// source of truth); no separate CONFIG.email copy is kept.
|
|
213
215
|
if (this.email) {
|
|
214
216
|
try {
|
|
215
|
-
const currentUser = await getCurrentUser();
|
|
216
|
-
await currentUser.setConfig({ email: this.email });
|
|
217
|
-
|
|
218
217
|
// Trigger verification email send with current origin
|
|
219
218
|
const origin = typeof window !== 'undefined' ? window.location.origin : undefined;
|
|
220
219
|
const verificationResult = await sendVerificationEmail(this.username, this.email, origin);
|
|
@@ -14,9 +14,11 @@ export {
|
|
|
14
14
|
sendVerificationEmail,
|
|
15
15
|
verifyEmail,
|
|
16
16
|
getUserStatus,
|
|
17
|
+
resolveLoginIdentifier,
|
|
17
18
|
requestPasswordReset,
|
|
18
19
|
resetPassword,
|
|
19
20
|
type AuthResponse,
|
|
20
21
|
type VerifyEmailResponse,
|
|
21
22
|
type UserStatusResponse,
|
|
23
|
+
type ResolveLoginResponse,
|
|
22
24
|
} from '../../services/authAPI';
|
package/src/services/authAPI.ts
CHANGED
|
@@ -68,6 +68,20 @@ export interface UserStatusResponse extends AuthResponse {
|
|
|
68
68
|
entitlements?: UserEntitlements;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
export interface ResolveLoginResponse extends AuthResponse {
|
|
72
|
+
username?: string;
|
|
73
|
+
/**
|
|
74
|
+
* HTTP status of the resolve call on failure, so callers can distinguish a
|
|
75
|
+
* genuine credential rejection from a system/service failure. The server
|
|
76
|
+
* returns a uniform 401 for every real auth failure (unknown identifier,
|
|
77
|
+
* unverified email, wrong password — no enumeration leak); anything else
|
|
78
|
+
* (404 = route not deployed, 5xx, or `undefined` for a network error) means
|
|
79
|
+
* the resolver itself is unavailable, not that the credentials were wrong.
|
|
80
|
+
* Undefined on success.
|
|
81
|
+
*/
|
|
82
|
+
status?: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
71
85
|
/**
|
|
72
86
|
* Triggers verification email send for a newly created account.
|
|
73
87
|
*
|
|
@@ -127,14 +141,65 @@ export async function verifyEmail(token: string): Promise<VerifyEmailResponse> {
|
|
|
127
141
|
});
|
|
128
142
|
|
|
129
143
|
if (!response.ok) {
|
|
144
|
+
// Prefer the server's explanatory message (expired token, email already
|
|
145
|
+
// verified on another account, …) over a bare HTTP status.
|
|
146
|
+
let serverError: string | undefined;
|
|
147
|
+
try {
|
|
148
|
+
serverError = (await response.json())?.error;
|
|
149
|
+
} catch {
|
|
150
|
+
// non-JSON body — fall through to the generic message
|
|
151
|
+
}
|
|
130
152
|
return {
|
|
131
153
|
ok: false,
|
|
132
|
-
error: `HTTP ${response.status}: ${response.statusText}`,
|
|
154
|
+
error: serverError || `HTTP ${response.status}: ${response.statusText}`,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return await response.json();
|
|
159
|
+
} catch (error) {
|
|
160
|
+
return {
|
|
161
|
+
ok: false,
|
|
162
|
+
error: error instanceof Error ? error.message : 'Network error',
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Resolve a login identifier (username OR email) to a couch username, gated on
|
|
169
|
+
* the correct password. Returns the username only on success; every failure is
|
|
170
|
+
* a uniform "invalid credentials" (no account-enumeration leak). Used by the
|
|
171
|
+
* login form before calling user.login() when the identifier is an email.
|
|
172
|
+
*/
|
|
173
|
+
export async function resolveLoginIdentifier(
|
|
174
|
+
identifier: string,
|
|
175
|
+
password: string
|
|
176
|
+
): Promise<ResolveLoginResponse> {
|
|
177
|
+
try {
|
|
178
|
+
const response = await fetch(`${getApiBase()}/auth/resolve-login`, {
|
|
179
|
+
method: 'POST',
|
|
180
|
+
headers: { 'Content-Type': 'application/json' },
|
|
181
|
+
credentials: 'include',
|
|
182
|
+
body: JSON.stringify({ identifier, password }),
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
let serverError: string | undefined;
|
|
187
|
+
try {
|
|
188
|
+
serverError = (await response.json())?.error;
|
|
189
|
+
} catch {
|
|
190
|
+
// non-JSON body — fall through to the generic message
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
ok: false,
|
|
194
|
+
status: response.status,
|
|
195
|
+
error: serverError || `HTTP ${response.status}: ${response.statusText}`,
|
|
133
196
|
};
|
|
134
197
|
}
|
|
135
198
|
|
|
136
199
|
return await response.json();
|
|
137
200
|
} catch (error) {
|
|
201
|
+
// Network / fetch failure — no HTTP status. Left undefined so callers treat
|
|
202
|
+
// it as a system failure, not a credential rejection.
|
|
138
203
|
return {
|
|
139
204
|
ok: false,
|
|
140
205
|
error: error instanceof Error ? error.message : 'Network error',
|